#!/usr/bin/python
#
# Code editor based on the GtkSourceBuffer widget.
# (c) Jon Dowdall 2008, 2009
#
# TODO:
# Complete auto macro code
# Increase context data included in saved context
# Add macro editing
# Combine meta data classes and context classes
# Add address bar widget
# Add man page lookup
# Enable spell checking of comments
# Test ability to write bz2 files
# Context view dialog
# Create additional language classes
# Integrate AST parser with language managers
# Add context selection to replace all button (line, selection, screen, visable, file)
#
# Maybe:
# Make search widget detachable
# Add auto save of file content on exit
# Add definition look up of functions for C/C++ and python
# Add 'TODO' widget
#

"""Python code editor based on GtkSourceBuffer."""

import operator
import copy
import datetime
import difflib
import file_monitor
import fnmatch
import glob
import pygtk
pygtk.require('2.0')
import gobject
import gtk
import gtksourceview2
import bz2
import gzip
import hashlib
import locale
import log
import os
import pango
import re
import socket
import stat
import subprocess
import sys
import urllib
import urlparse
import xml.dom.minidom
import xml.sax

from optparse import OptionParser

try:
    from dragndrop import Drop_Target
    import dragndrop
except:
    print "dragndrop import failed"
    pass

contexts = {}

defaults = {'config_file'    : '~/.code_edit/configuration.xml',
            'context_file'   : '~/.code_edit/sessions.xml',
            'contexts'       : 'Working',
            'font'           : 'Courier New 10',
            'interactive_search'  : True,
            'reg_ex_search'  : True,
            'tab_width'      : 4,
            'tab_character'  : ' ',
            'image_root'     : '../oxygen/',
            'search_history' : '',
            'replace_history': '',
            'auto_fold'      : True,
            'command_port'   : 5000}
            
cssc_root = 'C:/data/cygwin/usr/local/libexec/cssc/'

c_types = ["void", "unsigned", "bool", "char", "short", "int", "long", "float", "double"]
preprocessor_words = ["#defined", "#endif", "#if", "#ifdef", "#ifndef", "#include"]
c_reserved_words = ["and", "and_eq", "asm", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "class",
                    "compl", "const", "const_cast", "continue", "default", "delete", "do", "double", "dynamic_cast",
                    "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if",
                    "inline", "int", "long", "mutable", "namespace", "new", "not", "not_eq", "operator", "or", "or_eq",
                    "private", "protected", "public", "register", "reinterpret_cast", "return", "short", "signed",
                    "sizeof", "static", "static_cast", "struct", "switch", "template", "this", "throw", "true", "try",
                    "typdef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t",
                    "while", "xor", "xor_eq"]

def find(func, seq):
    """Return first item in sequence where func(item) == True."""
    for item in seq:
        if func(item):
            return item

def match_pairs (string, start_string, end_string, level=0, start=0, end=0):
        """Return a list of tuples indicating character positions of
        matching pairs of characters"""
        pattern = re.compile("(?P<start>%s)|(?P<end>%s)" % (start_string, end_string))

        if end == 0:
            end = len(string)

        count = 0
        result = []
        for match in pattern.finditer(string[start:end]):
            if match.group("start"):
                if start_string == end_string:
                    count = 1 - count
                    if count:
                        start_pos = match.start() + start
                    else:
                        end_pos = match.end() + start
                        result.append((start_pos, end_pos))
                else:
                    if count == level:
                        start_pos = match.start() + start
                    count += 1

            if match.group("end"):
                if count:
                    count -= 1
                    if count == level:
                        end_pos = match.end() + start
                        result.append((start_pos, end_pos))
        return result

def build_menu(menu_definition, bar = False):
    """Create a popup menu for the task elements of the tree.
    Menu contains, add task, add sub-task, delete task.

    """
    if bar:
        menu = gtk.MenuBar()
    else:
        menu = gtk.Menu()

    group = None

    for definition in menu_definition:
        text = definition[0]
        function = definition[1]
        if len(definition) > 2:
            data = definition[2]
        else:
            data = None
        if text == '---':
            item = gtk.SeparatorMenuItem()
            group = None
        elif text.startswith('>>>'):
            group = None
            item = gtk.MenuItem(text[3:])
            item.set_submenu(build_menu(function))
        elif text.startswith('oo'):
            item = gtk.RadioMenuItem(group, text[3:])
            if text[2] == 'x':
                item.set_active(True)
            item.connect("activate", function, data)
            group = item
        elif text.startswith('ox'):
            item = gtk.CheckMenuItem(text[3:])
            if text[2] == 'x':
                item.set_active(True)
            if data != None:
                item.connect("activate", function, data)
            else:
                item.connect("activate", function)
            group = None
        else:
            group = None
            item = gtk.MenuItem(text)
            if data != None:
                item.connect("activate", function, data)
            else:
                item.connect("activate", function)
        menu.append(item)
        item.show()
    return menu

class Text_Popup(gtk.Window):
    """Popup widget for editing multiline text."""
    
    def __init__(self, content, parent):
        """Create an undecorated window containing a text view."""
        gtk.Window.__init__(self)
        self.set_transient_for(parent.get_toplevel())
        self.set_decorated(False)
        container = gtk.ScrolledWindow()
        container.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        container.set_shadow_type(gtk.SHADOW_IN)
        self.text = gtk.TextView()
        self.text.get_buffer().set_text(content)
        container.add(self.text)
        self.add(container) 
        x, y, width, height = self.get_dimensions(parent)
        self.move(x, y)
        self.resize(width, height)
        self.connect('focus-out-event', self.exit_cb)
        self.show_all()

    def exit_cb(self, widget, event):
        """Remove the window when the focus is lost."""
        self.destroy()

    def get_dimensions(self, parent):
        """Determine the dimensions based on parent widget."""
        x, y = parent.window.get_origin()
        geometry = parent.window.get_geometry()        
        return (x, y, geometry[2], geometry[3])

class Multiline_Entry(gtk.Entry):
    """An entry widget with a multiline popup window."""

    def __init__(self, max=0):
        """Create an entry widget that will popup a text edit window for multi line entries."""
        gtk.Entry.__init__(self, max)
        self.connect('activate', self.activate_cb)
        self.connect('focus-in-event', self.focus_cb)
        self.text = ''

    def activate_cb(self, widget):
        """On activation append line feed and popup text entry."""
        self.text = self.get_text() + '\n'
        self.show_popup()

    def focus_cb(self, widget, event):
        """Popup text entry if multiline text has been entered."""
        if '\n' in self.text:
            self.show_popup()

    def show_popup(self):
        """Create an undecorated window containing a text view."""
        self.popup = gtk.Window()
        self.popup.set_transient_for(self.get_toplevel())
        self.popup.set_decorated(False)
        container = gtk.ScrolledWindow()
        container.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        container.set_shadow_type(gtk.SHADOW_IN)
        self.text_window = gtk.TextView()
        self.text_window.get_buffer().set_text(self.text)
        container.add(self.text_window)
        self.popup.add(container) 
        x, y, width, height = self.get_dimensions()
        self.popup.move(x, y)
        self.popup.resize(width, height)
        self.popup.connect('focus-out-event', self.exit_cb)
        self.popup.connect('key-release-event', self.key_release_cb)
        self.popup.show_all()

    def exit_cb(self, widget, event):
        """Remove the window when the focus is lost."""
        self.hide_popup()

    def hide_popup(self):
        """Remove the popup, updating the contents of the entry."""
        text_buffer = self.text_window.get_buffer()
        self.text = text_buffer.get_text(*text_buffer.get_bounds())
        self.set_text(self.text)
        self.popup.destroy()

    def get_dimensions(self):
        """Determine the dimensions based on parent widget."""
        x, y = self.window.get_origin()
        geometry = self.window.get_geometry()        
        return (x, y, geometry[2], geometry[3])

    def key_release_cb(self, widget, event):
        """Propogate the key release as if it came from the entry widget."""
        text_buffer = self.text_window.get_buffer()
        self.text = text_buffer.get_text(*text_buffer.get_bounds())
        self.set_text(self.text)
        self.emit('key-release-event', event)
        if '\n' not in self.text:
            self.hide_popup()
            self.set_position(-1)

class Multiline_Combo_Box(gtk.ComboBoxEntry):
    """An combo box with a multiline entry widget."""

    history = None
    popup = None

    def __init__(self, model=None):
        """Create an entry widget that will popup a text edit window for multi line entries."""
        gtk.ComboBoxEntry.__init__(self, model)
        if not model:
            self.history = gtk.ListStore(str, str)
            self.set_model(self.history)
            self.set_text_column(0)

        self.child.connect('activate', self.activate_cb)
        self.child.connect('focus-in-event', self.focus_in_cb)
        self.text = ''
        self.valid = True

    def activate_cb(self, widget):
        """On activation append line feed and popup text entry."""
        self.text = self.child.get_text() + '\n'
        self.show_popup()

    def focus_in_cb(self, widget, event):
        """Popup text entry if multiline text has been entered."""
        if '\n' in self.text:
            self.show_popup()
            text_buffer = self.text_window.get_buffer()
            text_buffer.select_range(*text_buffer.get_bounds())

    def set_valid(self, is_valid):
        """Change the display to indicate the data validity."""
        self.valid = is_valid
        if is_valid:
            if self.popup:
                text_buffer = self.text_window.get_buffer()
                text_buffer.remove_tag(self.error_tag, *text_buffer.get_bounds())
            self.child.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("Black"))
        else:
            if self.popup:
                text_buffer = self.text_window.get_buffer()
                text_buffer.apply_tag(self.error_tag, *text_buffer.get_bounds())
            self.child.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("Red"))

    def add_to_history(self):
        """Append the current text to the history."""
        if not self.history:
            self.history = gtk.ListStore(str, str)
            self.set_model(self.history)
            self.set_text_column(0)

        iter = self.history.get_iter_first()
        if iter:
            value = self.history.get_value(iter, 1)
            if self.text.find(value) != -1 or value.find(self.text) != -1:
                self.history.set(iter, 0, self.text.replace('\n', u"\u2026"), 1, self.text)
            else:
                self.history.prepend((self.text.replace('\n', u"\u2026"), self.text))
        else:
            self.history.prepend((self.text.replace('\n', u"\u2026"), self.text))

    def get_text(self):
        """Returns the current contents."""
        if not self.popup:
            self.text = self.child.get_text().replace(u"\u2026", '\n')
        self.add_to_history()
        return self.text

    def show_popup(self):
        """Create an undecorated window containing a text view."""
        self.popup = gtk.Window()
        self.popup.set_transient_for(self.get_toplevel())
        self.popup.set_decorated(False)
        container = gtk.ScrolledWindow()
        container.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        container.set_shadow_type(gtk.SHADOW_IN)
        self.text_window = gtk.TextView()
        text_buffer = self.text_window.get_buffer()
        text_buffer.set_text(self.text)
        self.error_tag = text_buffer.create_tag('re_error',
                                                foreground = "red",
                                                background = "white")

        if not self.valid:
            text_buffer.apply_tag(self.error_tag, *text_buffer.get_bounds())
        container.add(self.text_window)
        self.popup.add(container) 
        x, y, width, height = self.get_dimensions()
        self.popup.move(x, y)
        self.popup.resize(width, height)
        self.popup.connect('focus-out-event', self.exit_cb)
        self.popup.connect('key-release-event', self.key_release_cb)
        self.popup.show_all()

    def exit_cb(self, widget, event):
        """Remove the window when the focus is lost."""
        self.hide_popup()

    def hide_popup(self):
        """Remove the popup, updating the contents of the entry."""
        if self.popup:
            text_buffer = self.text_window.get_buffer()
            self.text = text_buffer.get_text(*text_buffer.get_bounds())
            self.child.set_text(self.text.replace('\n', u"\u2026"))
            self.popup.destroy()
            self.popup = None

    def get_dimensions(self):
        """Determine the dimensions based on parent widget."""
        x, y = self.child.window.get_origin()
        geometry = self.child.window.get_geometry()        
        return (x, y, geometry[2], geometry[3])

    def key_release_cb(self, widget, event):
        """Propogate the key release as if it came from the entry widget."""
        text_buffer = self.text_window.get_buffer()
        self.text = text_buffer.get_text(*text_buffer.get_bounds())
        self.child.set_text(self.text)
        if '\n' not in self.text:
            self.hide_popup()
            self.child.set_position(-1)
        self.emit('key-release-event', event)

class Search_Functions(gtk.Frame):
    """Class containing the widgets associated with search actions."""

    def __init__(self):
        """Create and display the widgets."""
        gtk.Frame.__init__(self)
        self.set_shadow_type(gtk.SHADOW_NONE)
        self.action_group = gtk.VBox()
        
        self.reg_exp_text = Multiline_Combo_Box() #gtk.combo_box_entry_new_text()
        self.reg_exp_text.connect("key-release-event", self.key_release_cb)
        self.reg_exp_text.connect("focus-in-event", self.focus_in_cb)
        self.reg_exp_text.connect("focus-out-event", self.focus_out_cb)
        self.action_group.pack_start(self.reg_exp_text, True, True, 1)

        self.actions = gtk.HBox()
        self.action_group.pack_start(self.actions, False, False, 1)
        self.interactive_button = gtk.CheckButton("Interactive")
        self.interactive_button.set_active(bool(config.interactive_search))
        self.interactive_button.connect('toggled', self.interactive_toggled_cb)
        self.actions.pack_start(self.interactive_button, False, True, 1)

        self.regexp_button = gtk.CheckButton("RegEx")
        self.regexp_button.set_active(bool(config.reg_ex_search))
        self.regexp_button.connect('toggled', self.regexp_toggled_cb)
        self.actions.pack_start(self.regexp_button, False, True, 1)

        self.search_pattern = ""
        self.search_history = config.search_history.split('\n')
        self.replace_history = config.replace_history.split('\n')

        self.first_button = gtk.Button("Find First")
        self.first_button.connect("clicked", self.find_first_cb)
        self.actions.pack_start(self.first_button, False, True, 1)

        self.next_button = gtk.Button("Find Next")
        self.next_button.connect("clicked", self.find_next_cb)
        self.actions.pack_start(self.next_button, False, True, 1)

        self.previous_button = gtk.Button("Find Previous")
        self.previous_button.connect("clicked", self.find_previous_cb)
        self.actions.pack_start(self.previous_button, False, True, 1)

        self.pair_button = gtk.Button("Pair")
        self.pair_button.connect("clicked", self.find_pairs_cb)
        self.actions.pack_start(self.pair_button, False, True, 1)

        self.replace_find_button = gtk.Button("Replace")
        self.replace_find_button.connect("clicked", self.replace_find_cb)
        self.actions.pack_start(self.replace_find_button, False, True, 1)

        self.replace_all_button = gtk.Button("Replace All")
        self.replace_all_button.connect("clicked", self.replace_all_cb)
        self.actions.pack_start(self.replace_all_button, False, True, 1)

        self.select_button = gtk.Button("Select All Matches")
        self.select_button.connect("clicked", self.select_cb)
        self.actions.pack_start(self.select_button, False, True, 1)

        self.show_matches = gtk.Button("Show Matches")
        self.show_matches.connect("clicked", self.show_matches_cb)
        self.actions.pack_start(self.show_matches, False, True, 1)

        self.hide_matches = gtk.Button("Hide Matches")
        self.hide_matches.connect("clicked", self.hide_matches_cb)
        self.actions.pack_start(self.hide_matches, False, True, 1)

        label = gtk.Label("Context Lines:")
        self.actions.pack_start(label, False, True, 1)
        self.lines_before = gtk.SpinButton()
        self.lines_before.set_tooltip_text('Lines Before')
        self.lines_before.get_adjustment().set_all(0, 0, 20, 1, 1, 0)
        self.actions.pack_start(self.lines_before, False, True, 1)

        self.lines_after = gtk.SpinButton()
        self.lines_after.set_tooltip_text('Lines After')
        self.lines_after.get_adjustment().set_all(0, 0, 20, 1, 1, 0)
        self.actions.pack_start(self.lines_after, False, True, 1)

        self.show_all = gtk.Button("Show All")
        self.show_all.connect("clicked", self.show_all_cb)
        self.actions.pack_start(self.show_all, False, True, 1)

        self.replacement_text = Multiline_Combo_Box()
        self.action_group.pack_start(self.replacement_text)

        self.add(self.action_group)

    def show_popup(self, text, parent):
        """Displays a popup window for editing multiline text."""
        Text_Popup(text, parent)

    def interactive_toggled_cb(self, button):
        """Processes a change of state of the interactive search setting."""
        config.interactive_search = self.interactive_button.get_active()

    def regexp_toggled_cb(self, button):
        """Processes a change of state of the regular expresion setting."""
        if self.regexp_button.get_active():
            reg_exp = self.reg_exp_text.get_text()
            flags = 0
            if reg_exp == reg_exp.lower():
                flags = flags | re.IGNORECASE
            try:
                self.search_pattern = re.compile(reg_exp, flags)
                self.reg_exp_ok()
                if  self.interactive_button.get_active():
                    if self.reg_exp_text.get_text():
                        widget = self.get_toplevel().tab_widget
                        active_page = widget.get_nth_page(widget.get_current_page())
                        self.re_find_first(active_page)
            except:
                self.reg_exp_error()
        else:
            # Not a regular expression so must be valid
            self.reg_exp_ok()
            if self.reg_exp_text.get_text():
                widget = self.get_toplevel().tab_widget
                active_page = widget.get_nth_page(widget.get_current_page())
                self.find_first(active_page)
                                
    def reg_exp_ok(self, description = None):
        """Indicate a problem with the regular expression by displaying it as red."""
        self.reg_exp_text.set_valid(True)

    def reg_exp_error(self, description = None):
        """Indicate a problem with the regular expression by displaying it as red."""
        self.reg_exp_text.set_valid(False)

    def replace_find_cb(self, widget):
        """Replace the text in the current code buffer based on the search criterior."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        if self.regexp_button.get_active():
            self.re_replace_find(active_page)
        else:
            self.replace_find(active_page)        

    def replace_find(self, active_page):
        """Replace the text in the current code buffer based on the search criterior."""
        buffer = active_page.get_buffer()
        selection = buffer.get_selection_bounds()
        if selection:
            reg_exp = self.reg_exp_text.get_text()
            updated_text = self.replacement_text.get_text()
            
            start, end = selection
            current_text = buffer.get_text(start, end)
            buffer.delete_interactive(selection[0], selection[1], active_page.get_view().get_editable())
            buffer.insert(start, updated_text) # N.B. start iter moved to end of inserted text
            buffer.place_cursor(start)
        self.find_next(active_page)

    def re_replace_find(self, active_page):
        """Replace the text in the current code buffer based on the search criterior."""
        buffer = active_page.get_buffer()
        selection = buffer.get_selection_bounds()
        if selection:
            reg_exp = self.reg_exp_text.get_text()
            reg_exp = reg_exp.replace("POD", "|".join(c_types))
            compiled = re.compile(reg_exp)

            new_string = self.replacement_text.get_text()

            start, end = selection
            current_text = buffer.get_text(start, end)
            if new_string[:4] == 'exec':
                # Ensure block is indented
                code = '\n  '.join(new_string.splitlines()[1:])
                exec 'def rep_func(match):\n  ' + code
                updated_text = compiled.sub(rep_func, current_text)
            else:
                updated_text = compiled.sub(new_string, current_text)
            buffer.delete_interactive(selection[0], selection[1], active_page.get_view().get_editable())
            buffer.insert(start, updated_text) # N.B. start iter moved to end of inserted text
            buffer.place_cursor(start)
        self.re_find_next(active_page)

    def replace_all_cb(self, widget):
        toplevel = self.get_toplevel()
        active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())
        if self.regexp_button.get_active():
            self.re_replace_all(active_page)
        else:
            self.replace_all(active_page)

    def replace_all(self, active_page):
        """Replace all occurances of the expression in the visible text."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()

        expression = self.reg_exp_text.get_text()
        replacement = self.replacement_text.get_text()

        if buffer.is_folded():
            start = buffer.get_start_iter()
            tag = buffer.get_tag_table().lookup('hidden')
            if start.begins_tag(tag):
                start.forward_to_tag_toggle(tag)
            end = start.copy()
            end.forward_to_tag_toggle(tag)
            while not start.is_end():
                current_text = buffer.get_text(start, end, False)
                new_text = current_text.replace(expression, replacement)
                buffer.delete(start, end)
                buffer.insert(start, new_text)
                end.forward_to_tag_toggle(tag)
                start = end.copy()
                end = start.copy()
                end.forward_to_tag_toggle(tag)                
        else:
            start, end = buffer.get_bounds()
            current_text = buffer.get_text(start, end, False)
            new_text = current_text.replace(expression, replacement)
            buffer.set_text(new_text)
        active_page.parse()

    def re_replace_all(self, active_page):
        """Replace the text in the current code buffer based on the search criterior."""
        reg_exp = self.reg_exp_text.get_text()
        reg_exp = reg_exp.replace("POD", "|".join(c_types))

        compiled = re.compile(reg_exp)
        new_string = self.replacement_text.get_text()

        buffer = active_page.get_buffer()
        buffer.remove_tag_by_name('selection', *buffer.get_bounds())
        current_text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
        if new_string[:4] == 'exec':
            # Ensure block is indented
            code = '\n  '.join(new_string.splitlines()[1:])
            exec 'def rep_func(match):\n  ' + code
            updated_text = compiled.sub(rep_func, current_text)
        else:
            updated_text = compiled.sub(new_string, current_text)
        buffer.set_text(updated_text)

    def find_pairs_cb(self, widget):
        """Find matching pairs."""
        toplevel = self.get_toplevel()
        active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())
        self.re_find_pairs(active_page)

    def re_find_pairs(self, active_page):
        """Find paired text identified by contined ellipses."""
        buffer = active_page.get_buffer()
        buffer.remove_tag_by_name('selection', *buffer.get_bounds())
        current_text = buffer.get_text(*buffer.get_bounds())

        reg_exp = self.reg_exp_text.get_text()
        reg_exp = reg_exp.replace("POD", "|".join(c_types))

        position = reg_exp.find('...')
        if position != -1:
            start = reg_exp[:position]
            end = reg_exp[position + 3:]
            pairs = match_pairs(current_text, start, end)
            for start, end in pairs:
                buffer.apply_tag('selection',
                                 buffer.get_iter_at_offset(start),
                                 buffer.get_iter_at_offset(end))
        else:
            log.log.append(log.Event('Warning', "Couldn't find '...' in %s" % (reg_exp)))

    def find_next_cb(self, widget):
        """Finds the next copy of the expression."""
        toplevel = self.get_toplevel()
        active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())

        buffer = active_page.get_buffer()
        selection = buffer.get_selection_bounds()
        if selection:
            if selection[0].compare(selection[1]) == -1:
                buffer.place_cursor(selection[1])
            else:
                buffer.place_cursor(selection[0])

        if self.regexp_button.get_active():
            self.re_find_next(active_page)
        else:
            self.find_next(active_page)

    def find_next(self, active_page):
        """Find next occurance of the expression."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()
        
        cursor = buffer.get_iter_at_mark(buffer.get_insert())
        start, end = buffer.get_bounds()

        search_text = buffer.get_text(cursor, end, False)
        offset = cursor.get_offset()
        expression = self.reg_exp_text.get_text()
        position = search_text.find(expression)
        if position > -1:
            iter = buffer.set_selection(position + offset, len(expression))
            view.scroll_to_iter(iter, 0.0, use_align=True, xalign=1.0, yalign=0.25)

    def re_find_next(self, active_page):
        """Find next occurance of search expression."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()

        start = buffer.get_iter_at_mark(buffer.get_insert())
        search_text = buffer.get_text(start, buffer.get_end_iter(), False)

        reg_exp = self.reg_exp_text.get_text()
        reg_exp = reg_exp.replace("POD", "|".join(c_types))

        # Regular expression may only be partially formed so don't complain if it
        # doesn't compile
        match = False
        try:
            # if the regular expression has no upper case characters the ignore case
            flags = 0
            if reg_exp == reg_exp.lower():
                flags = flags | re.IGNORECASE
            self.search_pattern = re.compile(reg_exp, flags)
            match = self.search_pattern.search(search_text)
            
            self.reg_exp_ok()
            if match:
                active_page.meta.bookmark(None)
                start = buffer.set_selection(start.get_offset() + match.start(),
                                             len(match.group(0)))
                view.scroll_to_iter(start, 0.0, use_align=True, xalign=1.0, yalign=0.25)

        except re.error:
            # Indicate the regular expression isn't valid
            self.reg_exp_error()
        finally:
            return match

    def find_previous_cb(self, widget):
        """Find the previous copy of the expression."""
        toplevel = self.get_toplevel()
        active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())

        buffer = active_page.get_buffer()
        selection = buffer.get_selection_bounds()
        if selection:
            if selection[0].compare(selection[1]) == -1:
                buffer.place_cursor(selection[0])
            else:
                buffer.place_cursor(selection[1])

        if self.regexp_button.get_active():
            self.re_find_previous(active_page)
        else:
            self.find_previous(active_page)

    def find_previous(self, active_page):
        """Find previous occurance of the expression."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()

        cursor = buffer.get_iter_at_mark(buffer.get_insert())
        current_text = buffer.get_text(buffer.get_start_iter(), cursor, False)
        expression = self.reg_exp_text.get_text()
        position = current_text.rfind(expression)
        if position > -1:
            iter = buffer.set_selection(position, len(expression))
            view.scroll_to_iter(iter, 0.0, use_align=True, xalign=1.0, yalign=0.25)

    def re_find_previous(self, active_page):
        """Find next occurance of previous search value."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()

        cursor = buffer.get_iter_at_mark(buffer.get_insert())
        search_text = buffer.get_text(buffer.get_start_iter(), cursor)

        reg_exp = self.reg_exp_text.get_text()
        reg_exp = reg_exp.replace("POD", "|".join(c_types))

        # Regular expression may only be partially formed so don't complain if it
        # doesn't compile
        try:
            # if the regular expression has no upper case characters the ignore case
            flags = 0
            if reg_exp == reg_exp.lower():
                flags = flags | re.IGNORECASE
            self.search_pattern = re.compile(reg_exp, flags)
            self.reg_exp_ok()
            matches = [match for match in self.search_pattern.finditer(search_text)]
            if matches:
                match = matches[-1]
                active_page.meta.bookmark(None)
                start = buffer.get_iter_at_offset(match.start())
                end = start.copy()
                end.forward_chars(match.end() - match.start())
                buffer.select_range(end, start)
                view.scroll_to_iter(end, 0.0, use_align=True, xalign=1.0, yalign=0.25)
        except re.error:
            # Apply the error tag to indicate the regular expression isn't valid
            self.reg_exp_error()

    def entry_activated_cb(self, entry):
        """Displays the text entry popup if necessary."""
        text = entry.get_text() + '\n'
        entry.set_text(text)
        self.show_popup(text, entry)

    def focus_in_cb(self, widget, event):
        """Store the current buffer location to return to if search cancelled."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        
        text_buffer = active_page.get_buffer()
        start = text_buffer.create_mark(None, text_buffer.get_iter_at_mark(text_buffer.get_insert()), False)
        end = text_buffer.create_mark(None, text_buffer.get_iter_at_mark(text_buffer.get_selection_bound()), False)
        active_page.meta.bookmark((start, end))

    def focus_out_cb(self, widget, event):
        """Clear search related selections in the active buffer."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        text_buffer = active_page.get_buffer()
        text_buffer.remove_tag(text_buffer.selection, *text_buffer.get_bounds())

    def key_release_cb(self, widget, event):
        """Update interactive find whenever a key has been released."""
        if event.keyval == gtk.keysyms.Escape:
            self.reg_exp_text.hide_popup()
            toplevel = self.get_toplevel()
            active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())
            if event.state & gtk.gdk.SHIFT_MASK:
                #position = active_page.get_buffer().reset_position()
                #active_page.get_view().scroll_to_iter(position, 0.0, use_align=True, xalign=1.0, yalign=0.25)
                pass
            active_page.get_view().grab_focus()
        elif self.interactive_button.get_active():
            toplevel = self.get_toplevel()
            active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())
            the_buffer = active_page.get_buffer()
            #the_buffer.set_position()

            if self.regexp_button.get_active():
                self.re_find_next(active_page)
            else:
                self.find_next(active_page)
        else:
            reg_exp = self.reg_exp_text.get_text()
            reg_exp = reg_exp.replace("POD", "|".join(c_types))

            flags = 0
            if reg_exp == reg_exp.lower():
                flags = flags | re.IGNORECASE
            try:
                self.search_pattern = re.compile(reg_exp, flags)                
                # If we got here the regular expression must be valid
                self.reg_exp_ok()
            except re.error:
                # Apply the error tag to indicate the regular expression isn't valid
                self.reg_exp_error()
       

    def find_first_cb(self, widget):
        """Finds the first copy of the expression."""
        toplevel = self.get_toplevel()
        active_page = toplevel.tab_widget.get_nth_page(toplevel.tab_widget.get_current_page())
        if self.regexp_button.get_active():
            result = self.re_find_first(active_page)
        else:
            result = self.find_first(active_page)
        return result

    def find_first(self, active_page):
        """Find first occurance of the expression."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()
        buffer.search_start = None
        buffer.remove_tag(buffer.selection, *buffer.get_bounds())

        start, end = buffer.get_bounds()
        current_text = buffer.get_text(start, end, False)
        expression = self.reg_exp_text.get_text()
        position = current_text.find(expression)
        result = False
        if position > -1:
            iter = buffer.set_selection(position, len(expression))
            view.scroll_to_iter(iter, 0.0, use_align=True, xalign=1.0, yalign=0.25)
            result = True
        return result

    def re_find_first(self, active_page):
        """Find first occurance of the regular expression."""
        result = False
        view = active_page.get_view()
        buffer = active_page.get_buffer()
        buffer.search_start = None
        buffer.remove_tag(buffer.selection, *buffer.get_bounds())

        start, end = buffer.get_bounds()
        current_text = buffer.get_text(start, end, False)

        reg_exp = self.reg_exp_text.get_text()
        reg_exp = reg_exp.replace("POD", "|".join(c_types))

        flags = 0
        if reg_exp == reg_exp.lower():
            flags = flags | re.IGNORECASE
        # Regular expression may only be partially formed so don't complain if it
        # doesn't compile
        # If the regular expression has no upper case characters the ignore case
        try:
            self.search_pattern = re.compile(reg_exp, flags)
            
            # If we got here the regular expression must be valid
            self.reg_exp_ok()
            match = self.search_pattern.search(current_text)
            if match:
                #current_context.windows.values()[0].target.bookmark(None)
                buffer.set_selection(match.start(), len(match.group(0)))
                view.scroll_to_mark(buffer.get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)
                result = True
        except re.error:
            # Apply the error tag to indicate the regular expression isn't valid
            self.reg_exp_error()
        return result

    def select_cb(self, widget):
        """Selects all text matching the search expression."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        if self.regexp_button.get_active():
            self.re_select(active_page)
        else:
            self.select(active_page)        

    def re_select(self, active_page):
        """Select all the text matching the current regular expression."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()

        buffer.remove_tag(buffer.selection, *buffer.get_bounds())
        current_text = unicode(buffer.get_slice(*buffer.get_bounds()))

        reg_exp = self.reg_exp_text.get_text()
        reg_exp = reg_exp.replace("POD", "|".join(c_types))

        # If the regular expression has no upper case characters the ignore case
        flags = 0
        if reg_exp == reg_exp.lower():
            flags = flags | re.IGNORECASE
        try:
            pattern = re.compile(reg_exp, flags)
            self.reg_exp_ok()

            copied_text = []
            for match in pattern.finditer(current_text):
                start = buffer.get_iter_at_offset(match.start())
                end = buffer.get_iter_at_offset(match.end())
                buffer.apply_tag(buffer.selection, start, end)
                copied_text.append(buffer.get_text(start, end))
            if copied_text:
                gtk.Clipboard().set_text('\n'.join(copied_text))

        except re.error:
            # Apply the error tag to indicate the regular expression isn't valid
            self.reg_exp_error()            

    def select(self, active_page):
        """Select all the text matching the current search expression."""
        view = active_page.get_view()
        buffer = active_page.get_buffer()

        buffer.remove_tag(buffer.selection, *buffer.get_bounds())
        current_text = unicode(buffer.get_slice(*buffer.get_bounds()))

        copied_text = []
        pattern = self.reg_exp_text.get_text()
        position = current_text.find(pattern)
        while position != -1:
            start = buffer.get_iter_at_offset(position)
            end = buffer.get_iter_at_offset(position + len(pattern))
            buffer.apply_tag(buffer.selection, start, end)
            copied_text.append(buffer.get_text(start, end))
            position = current_text.find(pattern, position + len(pattern))
        if copied_text:
            gtk.Clipboard().set_text('\n'.join(copied_text))

    def show_matches_cb(self, widget):
        """Hides text not matching the pattern."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        active_page.get_buffer().show_matches(self.reg_exp_text.get_text(),
                                              self.regexp_button.get_active(),
                                              int(self.lines_before.get_value()),
                                              int(self.lines_after.get_value()))

    def hide_matches_cb(self, widget):
        """Hides text matching the pattern."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        active_page.get_buffer().hide_matches(self.reg_exp_text.get_text(),
                                              self.regexp_button.get_active(),
                                              int(self.lines_before.get_value()),
                                              int(self.lines_after.get_value()))

    def show_all_cb(self, widget):
        """Reveals all hidden text."""
        tab_widget = self.get_toplevel().tab_widget
        active_page = tab_widget.get_nth_page(tab_widget.get_current_page())
        active_page.get_buffer().show_all()

def parse_grep(page):
    """Parse the current line as a result of a grep operation."""
    buffer = page.get_buffer()
    cursor = buffer.get_insert()
    iter = buffer.get_iter_at_mark(cursor)
    line_number = iter.get_line()
    start = buffer.get_iter_at_line(line_number)
    end = buffer.get_iter_at_line(line_number + 1)
    line = buffer.get_text(start, end)
    contents = line.split(':')
    if contents:
        if len(contents) > 1:
            link = 'file:' + buffer.meta.path.replace('\\', '/') + '/' + contents[0]
        if len(contents) > 2:
            # Check that item can be converted to a number
            try:
                link += '?line=%s' % (int(contents[1]))
            except ValueError:
                link += '#%s' % (contents[1])
        else:
            link += '#%s' % (contents[1])
        page.get_toplevel().load_link(link)

def parse_make(page):
    """Parse the current line as a result of a grep operation."""
    buffer = page.get_buffer()
    cursor = buffer.get_insert()
    iter = buffer.get_iter_at_mark(cursor)
    line_number = iter.get_line()
    start = buffer.get_iter_at_line(line_number)
    end = buffer.get_iter_at_line(line_number + 1)
    line = buffer.get_text(start, end)
    contents = line.split(':')
    if contents:
        if len(contents) > 1:
            link = 'file:' + buffer.meta.path.replace('\\', '/') + '/' + contents[0]
        if len(contents) > 2:
            # Check that item can be converted to a number
            try:
                link += '?line=%s' % (int(contents[1]))
            except ValueError:
                link += '#%s' % (contents[1])
        else:
            link += '#%s' % (contents[1])
        page.get_toplevel().load_link(link)

def find_module(root, module_name):
    """Returns the path to the file containing the module given by module_name."""
    path = None
    module_name = module_name.replace('.', '/')
    if not module_name[-3:] == '.py':
        module_name += '.py'
    if os.path.exists(root + '/' + module_name):
        path = root + '/' + module_name
    else:
        for item in sys.path:
            if os.path.exists(item + '/' + module_name):
                path = item + '/' + module_name
                break
    return path

def find_boundary(char, stop_at_dot):
    """Return true if char marks a identifier boundary."""
    ident_char = char.isalnum() or char == '_'
    if stop_at_dot:
        ident_char = ident_char or char == '.'
    return not ident_char 

def python_visit(page):
    """Look up the module / function at the cursor."""
    buffer = page.get_buffer()
    cursor = buffer.get_insert()
    iter = buffer.get_iter_at_mark(cursor)
    start = iter.copy()
    start.backward_find_char(find_boundary, True, None)
    start.forward_char()
    end = iter.copy()
    end.forward_find_char(find_boundary, False, None)
    
    line_number = iter.get_line()
    start = buffer.get_iter_at_line(line_number)
    end = buffer.get_iter_at_line(line_number + 1)
    line = buffer.get_text(start, end)
    contents = line.split()
    if contents:
        if contents[0] == 'import':
            filename = find_module(buffer.meta.path, contents[1])
            if filename:
                app_window.load_file(filename)

def recursive_sort(tuple_list):
    """Returns a sorted copy of the list of tuples."""
    tuple_list.sort(key = operator.itemgetter(0))
    for item in tuple_list:
        recursive_sort(item[2])

def add_to_store(store, item, parent):
    """Adds the item and it's sub list to the store."""
    parent = store.append(parent, item[0:2] + (Source_Component.image,))
    for sub_item in item[2]:
        add_to_store(store, sub_item, parent)

def parse_ada(store, buffer=None, filename=None, sort=False):
        """Parse the text in the supplied buffer to generate a list of definitions."""
        store.clear()
        if buffer:
            text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
        elif filename:
            pass
        else:
            log.log.append(log.Event("Warning", "Ada parser needs a buffer or filename to operate on."))
            return
        definition_pattern = re.compile(r"""(?P<type>package[ ]body|task[ ]body|procedure|function|protected[ ]body)
                                            \s+
                                            (?P<sig>(?P<name>\w+)\s*(\([\w\s:,;.\']*\))?\s*(return\s+\w+)?)
                                            \s+is\s+(?P<new>new)?
                                            |\s(?P<end>end)\s+(?P<end_name>\w+)\s*;""", re.VERBOSE)
        current_scope = ('GLOBAL', '', [])
        scope_stack = [current_scope]
        scope = scope_stack[-1][2]
        code = []
        offset = 0
        definitions = definition_pattern.finditer(text)
        for definition in definitions:
            if not definition.group('end'):
                if not definition.group('new'):
                    defn_type = definition.group('type')
                    block_name = definition.group('sig')
                    name = definition.group('name')
                    position = buffer.get_iter_at_offset(definition.start())
                    mark = buffer.create_mark(None, position)
                    full_sig = re.sub('\s+', ' ', '%s %s' % (defn_type, block_name))
                    current_scope = (full_sig, mark, [], name)
                    scope_stack.append(current_scope)
                    scope.append(current_scope)
                    scope = current_scope[2]
            else:
                name = definition.group('end_name')
                block_name = scope_stack[-1][3]
                if name == block_name:
                    scope_stack.pop()
                    if not scope_stack:
                        log.log.append(log.Event('Warning', 'unmatched "end" for %s!' % (block_name)))
                        scope_stack = [('GLOBAL', '', [])]
                    scope = scope_stack[-1][2]

        if sort:
            recursive_sort(scope)

        # Clear existing data.
        store.clear()
        for item in scope:
            add_to_store(store, item, None)

def c_find_func_def(page, function):
    """Open the file containing the specified function."""
    # Look in current file first
    buffer = page.get_buffer()
    view = page.get_view()
    pattern = re.compile("%s%s" % (function, """\s*\([^(]*\)\s*{"""))
    match = pattern.search(current_text)
    if match:
        #page.get_toplevel().bookmark(None)
        iter = buffer.get_iter_at_offset(match.start())
        buffer.place_cursor(buffer.get_iter_at_offset(match.end()))
        view.scroll_to_mark(buffer.get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)
        buffer.select_range(buffer.get_iter_at_offset(match.start()),
                            buffer.get_iter_at_offset(match.end()))

def c_fold(buffer, start_level=1, with_context=False):
    """Hide all but the current block."""
    cursor = buffer.get_iter_at_mark(buffer.get_insert())
    start = cursor.copy()
    end = cursor.copy()
    level = start_level
    while level:
        result = start.backward_search('{', 0)
        if not result:
            return None
        string = buffer.get_text(result[0], start)
        start = result[0]
        level += string.count('}')
        level -= string.count('{')

    level = start_level
    while level:
        result = end.forward_search('}', 0)
        if not result:
            return None
        string = buffer.get_text(end, result[1])
        end = result[1]
        level += string.count('{')
        level -= string.count('}')

    if with_context:
        result = start.backward_search('\n\n', 0)        
        if result:
            start = result[1]

    return start, end

def parse_c(store, text_buffer, sort=False):
    """Parse the text in the supplied buffer to generate a list of definitions."""
    store.clear()
    text = text_buffer.get_text(text_buffer.get_start_iter(),
                                text_buffer.get_end_iter())
    # Concatenate lines with whitespace to preserve character count
    text = text.replace('\\\n', '  ')

    markers = re.compile(r'([{};"#]|/\*|\*/|//)')
    current_block = 'Text'
    current_scope = ('GLOBAL', '', [])
    scope_stack = [current_scope]
    scope = scope_stack[-1][2]
    code = []
    label = ""
    offset = 0
    old_position = None
    for statement in markers.split(text):
        statement_offset = offset
        offset += len(statement)

        if current_block == 'Preprocessor' or current_block == 'Line Comment':
            if statement.count('\n'):
                 statement_offset += len(statement[:statement.index('\n')])
                 statement = statement[statement.index('\n'):]
                 current_block = 'Text'

        stripped_statement = statement.strip()
        if not stripped_statement:
            continue
        statement_offset += len(statement) - len(statement.lstrip())
            
        if stripped_statement == '#' and current_block == 'Text':
            current_block = 'Preprocessor'
        elif current_block != 'Quote' and stripped_statement == '/*':
            current_block = 'Comment'
        elif current_block != 'Quote' and stripped_statement == '*/':
            if current_block == 'Comment':
                current_block = 'Text'
            else:
                log.log.append(log.Event('Warining', '"*/" matched outside comment'))
        elif (current_block != 'Comment' and
              current_block != 'Preprocessor' and
              stripped_statement == '"'):
            if current_block != 'Quote':
                current_block == 'Quote'
            else:
                current_block = 'Text'
            label += '"'
        elif current_block == 'Text' and stripped_statement == '{':
            scope_stack.append(current_scope)
            if label:
                scope.append(current_scope)
                scope = current_scope[2]
                label = ""
        elif current_block == 'Text' and stripped_statement == '}':
            current_scope = scope_stack.pop()
            if not scope_stack:
                log.log.append(log.Event('Warning', 'unmatched "}"!'))
                scope_stack = [('GLOBAL', '', [])]
            if current_scope[0]:
                scope = scope_stack[-1][2]
                label = ""
        elif current_block == 'Text' and stripped_statement == '//':
            current_block = 'Line Comment'
        elif stripped_statement == ';':
            label = ""
        else:
            if current_block == 'Text':
                if len(statement.strip().split()):
                    #var_type = statement.strip().split()[0]
                    #name = " ".join(statement.strip().split()[1:])
                    position = text_buffer.get_iter_at_offset(statement_offset)
                    label += ' '.join([word.strip() for word in statement.split()])
                    mark = text_buffer.create_mark(label, position)
                    pos = label.find('(')
                    if pos:
                        start = label[:pos].rstrip().rfind(' ')
                    else:
                        start = 0
                    if start > 0 and pos > start:
                        markup = '<span foreground="dark green">' + label[:start] + '</span>'
                        markup += '<b>' + label[start:pos] + '</b>' + label[pos:]
                    else:
                        markup = label
                    current_scope = (markup, mark, [])
                    if len(scope_stack) > 1:
                        tag_name = str(scope_stack[1][0])
                        start = tag_name.find('(')
                        end = tag_name.rfind(')')
                        if start > 0 and end > start:
                            tag_name = tag_name[:start] + '(...' + tag_name[end:]
                        tag_name = 'scope:' + tag_name
                        if old_position:
                            tag = text_buffer.get_tag_table().lookup(tag_name)
                            if not tag:
                                tag = text_buffer.create_tag(tag_name)
                            text_buffer.apply_tag(tag, old_position, position)
                    old_position = position.copy()
            elif current_block == 'Quote':
                label += ' '.join([word.strip() for word in statement.split()])
 
    if sort:
        recursive_sort(scope)

    # Clear existing data.
    store.clear()
    for item in scope:        
        add_to_store(store, item, None)

def parse_cpp(store, text_buffer, sort=False):
    parse_c(store, text_buffer, sort)

class Language(object):

    menu = None
    parens = [('(', ')'), ('{', '}'), ('[', ']'), ('"', '"'), ("'", "'")]

    def __init__(self, text_buffer):
        self.buffer = text_buffer
        self.components = []
        self.tags = set()
        self.menu = [('Comment', self.comment_cb),
                     ('Uncomment', self.uncomment_cb)]

    def get_source(self):
        """Return the source text in the associated buffer."""
        return self.buffer.get_text(*self.buffer.get_bounds())

    def populate_store(self, store):
        """Fill the store based on the elements of the components list."""
        child_iter = store.get_iter_first()
        for element in self.components:
            child_iter = self.add_to_store(store, element, parent=None, position=child_iter)

    def add_to_store(self, store, item, parent, position):
        """Adds the item and it's sub list to the store."""
        row = item.tree_row()
        if position:
            text = store.get_value(position, 0)
            if row[0] != text:
                store.remove(position)
                if store.iter_is_valid(position):
                    position = store.insert_before(parent, position, row)
                else:
                    position = store.append(parent, row)
        else:
            position = store.append(parent, row)

        child_iter = store.iter_children(position)
        for child in item.children:
            child_iter = self.add_to_store(store, child, position, child_iter)

        position = store.iter_next(position)
        return position

    def remove_tags(self):
        """Remove the tags associated with identifying code components."""
        start, end = self.buffer.get_bounds()
        for tag in self.tags:
            self.buffer.remove_tag(tag, start, end)
            #self.buffer.get_tag_table().remove(tag)

    def add_tag(self, element):
        """Add a tags to the buffer for the element and its children."""
        tag_name = 'scope:' + element.full_name
        tag = self.buffer.get_tag_table().lookup(tag_name)
        if not tag:
            tag = self.buffer.create_tag(tag_name)
        self.tags.add(tag)
        for location in element.locations:
            start = self.buffer.get_iter_at_mark(location[0])
            if location[1]:
                end = self.buffer.get_iter_at_mark(location[1])
            else:
                end = self.buffer.get_end_iter()
            self.buffer.apply_tag(tag, start, end)
        for child in element.children:
            self.add_tag(child)

    def apply_tags(self):
        """Creates the tags used to identify the current code component."""
        self.remove_tags()
        for element in self.components:
            self.add_tag(element)  

    def comment_cb(self, item, view):
        """Comments out the currently selected block."""
        text_buffer = view.get_buffer()
        bounds = text_buffer.get_selection_bounds()
        if bounds:
            start, end = bounds
        else:
            start = text_buffer.get_iter_at_mark(text_buffer.get_insert())
            end = start.copy()

        if not start.starts_line():
            start.set_line(start.get_line())

        if not end.ends_line():
            end.forward_to_line_end()
        elif end.starts_line():
            end.backward_char()
#            end.set_line(end.get_line() - 1)
#            end.forward_to_line_end()
        
        mark = text_buffer.create_mark(None, end)
        
        text_buffer.begin_user_action()
        current = start.copy()
        while current.compare(text_buffer.get_iter_at_mark(mark)) == -1:
            text_buffer.insert(current, self.line_comment_start)
            current.forward_line()
        text_buffer.end_user_action()

        text_buffer.delete_mark(mark)
         
    def uncomment_cb(self, item, view):
        """Uncomment out the currently selected block.""" 
        text_buffer = view.get_buffer()
        bounds = text_buffer.get_selection_bounds()
        if bounds:
            start, end = bounds
        else:
            start = text_buffer.get_iter_at_mark(text_buffer.get_insert())
            end = start.copy()

        if not start.starts_line():
            start.set_line(start.get_line())

        if not end.ends_line():
            end.forward_to_line_end()
        elif end.starts_line():
            end.backward_char()

        mark = text_buffer.create_mark(None, end)
        
        text_buffer.begin_user_action()
        line_start = start.copy()
        while line_start.compare(text_buffer.get_iter_at_mark(mark)) == -1:
            line_end = line_start.copy()
            line_end.forward_to_line_end()
            line = text_buffer.get_text(line_start, line_end)
            if line.lstrip().startswith(self.line_comment_start):
                pos = line.find(self.line_comment_start)
                line_start.forward_chars(pos)
                end = line_start.copy()
                end.forward_chars(len(self.line_comment_start))
                text_buffer.delete(line_start, end)
            line_start.forward_line()
        text_buffer.end_user_action()
        text_buffer.delete_mark(mark)

class Nonterminal(object):
    """Represents an element of EBNF notation."""
    
    def __init__(self, name):
        """Initialise the components of the nonterminal."""
        self.meta_identifier = name
        self.definitions_list = []

    def parse(self, string):
        """Returns true if the string matches this symbols definition."""
        for element in self.defintions_list:
            if element.parse(string):
                return element
        return None

class Source_Component(object):
    """Manages elements within a structured language."""
    __slots__ = ['type', 'name', 'locations', 'parent', 'children']

    image = gtk.gdk.pixbuf_new_from_file('C:/data/oxygen/16x16/actions/underconstruction.png')

    def __init__(self, type_name, name):
        """Set the type and name of the component."""
        self.type = type_name
        self.name = name
        self.locations = []
        self.parent = None
        self.children = []

    def get_name(self):
        """Returns a string containing the name of the object."""
        return '%s: %s' % (self.type, self.name)

    def get_full_name(self):
        """Returns a fully qualified name for the component."""
        if self.parent:
            full_name = '%s.%s' % (self.parent.full_name, self.name)
        else:
            full_name = self.name
        return full_name
    
    full_name = property(get_full_name)

    def markup(self):
        """Returns a string containing the markup representation of the object."""
        markup = '<span foreground="dark green">%s:</span> ' % (self.type)
        markup += '<b>%s</b>' % (self.name)
        return markup

    def tree_row(self):
        """Returns a tuple containing the data used to fill a tree row."""
        markup = self.markup()
        location = self.locations[0][0]
        return (markup, location, self.image)

class C_Base_Manager(Language):
    """Manage operations common to C and C++"""

    def __init__(self, text_buffer):
        """Set the text buffer associated with this manager."""
        Language.__init__(self, text_buffer)
        self.menu += [('Find Header', self.find_header_cb)]

    def find_header_cb(self, item, view):
        """Interface function for menu access to find_header function."""
        header = self.find_header()
        if header:
            app_window = view.get_toplevel()
            app_window.load_file(header)

    def find_header(self):
        """Opens the header file for this source."""
        header_file = os.path.splitext(self.buffer.meta.full_path)[0] + '.h'
        if not os.path.exists(header_file):
            files = glob.glob('header_file' + '*')
            if len(files) == 1:
                header_file = files[0]
        if not os.path.exists(header_file):
            unit_name = os.path.splitext(os.path.basename(self.filename))[0]
            pattern = r'#include\s["<]([a-zA-Z0-9/_-]*' + unit_name + '\.h(pp)?)'
            source = self.get_source()
            match = pattern.search(source)
            if match:
                header_file = match.group(1)

        return header_file

class C_Manager(C_Base_Manager):
    """Manage operations applicable to C source code."""
    pass

class Cpp_Manager(C_Base_Manager):
    """Manage operations applicable to C++ source code."""
    pass

class Python_Manager(Language):

    line_comment_start = '#'

    class Import(Source_Component):
        """Data associated with an imported module."""
        image = gtk.gdk.pixbuf_new_from_file('C:/data/oxygen/16x16/actions/document-import.png')

        def __init__(self, name):
            Source_Component.__init__(self, 'Import', name)
            
        def find_module(self, root=''):
            """Returns the path to the file containing the module."""
            path = None
            module_name = module_name.replace('.', '/')
            if not module_name[-3:] == '.py':
                module_name += '.py'
            if os.path.exists(root + '/' + self.name):
                path = root + '/' + self.name
            else:
                for item in sys.path:
                    if os.path.exists(item + '/' + self.name):
                        path = item + '/' + self.name
                        break
            return path

    class Class(Source_Component):
        """Data associated with python classes."""
        __slots__ = ['bases']

        image = gtk.gdk.pixbuf_new_from_file('C:/data/oxygen/16x16/actions/archive-insert.png')
        
        def __init__(self, name, bases):
            """Initialise the class name."""
            Source_Component.__init__(self, 'Class', name)
            self.bases = bases
       
        def tree_row(self):
            """Returns a tuple containing the data used to fill a tree row."""
            markup, location, image = Source_Component.tree_row(self)
            return (markup + ' (%s)' % (','.join(self.bases)), location, self.image)

    class Function(Source_Component):
        """Data associated with python functions."""
        __slots__ = ['arguments']
        image = gtk.gdk.pixbuf_new_from_file('C:/data/oxygen/16x16/actions/exec.png')
        
        def __init__(self, name, arguments):
            """Initialise the class name."""
            Source_Component.__init__(self, 'Function', name)
            self.arguments = arguments

        def tree_row(self):
            """Returns a tuple containing the data used to fill a tree row."""
            markup, location, image = Source_Component.tree_row(self)
            return (markup + ' (%s)' % (','.join(self.arguments)), location, self.image)

    def __init__(self, text_buffer):
        """Set the text buffer associated with this manager."""
        Language.__init__(self, text_buffer)
        self.menu += [('Check Syntax', self.check_cb),
                      ('Execute All', self.execute_all_cb),
                      ('Execute Selection', self.execute_selection_cb)]

    def check_cb(self, item, view):
        """Check the syntax of the current buffer."""
        text_buffer = view.get_buffer()
        bounds = text_buffer.get_selection_bounds()
        if not bounds:
            bounds = text_buffer.get_bounds()
        selection = text_buffer.get_text(*bounds)
        selection.replace('\r', '')
        try:
            compile(selection, text_buffer.get_title(), 'exec')
        except (IndentationError, SyntaxError), compile_error:
            error_position = bounds[0].copy()
            error_position.forward_lines(compile_error.args[1][1] - 1)
            error_position.forward_chars(compile_error.args[1][2] - 1)
            text_buffer.place_cursor(error_position)
            view.get_toplevel().update_status(str(compile_error))
        else:
            view.get_toplevel().update_status('Syntax ok')

    def execute(self, text_buffer, execute_all=True):
        """Execute the buffer."""
        bounds = text_buffer.get_selection_bounds()
        if execute_all or not bounds:
            bounds = text_buffer.get_bounds()
        selection = text_buffer.get_text(*bounds)
        if selection:
            exec selection

    def execute_all_cb(self, item, view):
        """Execute the current buffer."""
        text_buffer = view.get_buffer()
        self.execute(text_buffer)

    def execute_selection_cb(self, item, view):
        """Execute the current selection or buffer if no selection."""
        text_buffer = view.get_buffer()
        self.execute(text_buffer, execute_all = False)

    def parse(self):
        """Generates the component tree from the buffer associated with this object."""
        text = self.buffer.get_text(*self.buffer.get_bounds())
        text = text.replace('\\\n','  ') # Insert spaces to keep character count consistent

        quoted_string1 = re.compile(r""""[^"]*\"|'[^']*'""")
        quoted_string2 = re.compile(r'""".*"""')
        braces = 0
        quote = 0
        paren1 = 0
        paren2 = 0
        line_start = 0
        line_end = -1
        statement = ''
        context_stack = [(None, 0)]
        previous_component = None
        self.components = []
        for line in text.splitlines():
            line_start = line_end + 1
            line_end = line_start + len(line)
            
            line1 = line.replace('\t', '    ')
            line_text = line1.strip()

            # Ignore blank lines and comment lines
            if not line_text or line_text.startswith('#'):
                continue

            line2 = quoted_string1.sub('', quoted_string2.sub('', line1))
            braces += line2.count('{') - line2.count('}')
            paren1 += line2.count('(') - line2.count(')')
            paren2 += line2.count('[') - line2.count(']')
            quote ^= line2.count('"') % 2

            if not statement:
                indent = len(line1) - len(line1.lstrip())
                
            statement += line_text
            if not (braces or paren1 or paren2 or quote or line[-1] == r"\\"):
                keyword = statement.split()[0]

                if keyword == 'class':
                    definition = self.Class
                elif keyword == 'def':
                    definition = self.Function
                elif keyword == 'import':
                    definition = self.Import
                else:
                    definition = None

                if context_stack[-1][0] and indent <= context_stack[-1][1]:
                    position = self.buffer.get_iter_at_offset(line_start)
                    position.backward_line()
                    mark = self.buffer.create_mark(None, position)

                    while indent < context_stack[-1][1]:
                        previous = context_stack.pop()[0]
                        previous.locations[0] = (previous.locations[0][0],
                                                 mark,
                                                 previous.locations[0][2])
                    if indent != context_stack[-1][1]:
                        log.log.append(log.Event('Warning', 'INDENT ERROR: %s' % (statement)))
                    else:
                        previous = context_stack[-1][0]
                        previous.locations[0] = (previous.locations[0][0],
                                                 mark,
                                                 previous.locations[0][2])

                if definition:
                    name = ' '.join(statement.split()[1:]).strip()
                    pos = name.find('(')
                    if pos != -1:
                        short_name = name[:pos].strip()
                        parameters = name[pos + 1:].split(',')
                        parameters[-1] = parameters[-1][:-2]
                    else:
                        short_name = name[:-1]
                        parameters = []

                    if definition == self.Import:
                        new_component = definition(name)
                    else:
                        new_component = definition(short_name, parameters)

                    if indent == context_stack[-1][1]:
                        if len(context_stack) == 1:
                            self.components.append(new_component)
                        else:
                            context_stack[-2][0].children.append(new_component)
                            new_component.parent = context_stack[-2][0]
                        context_stack[-1] = (new_component, indent)
                    elif indent > context_stack[-1][1]:
                        context_stack[-1][0].children.append(new_component)
                        new_component.parent = context_stack[-1][0]
                        context_stack.append((new_component, indent))
                    else:
                        while indent < context_stack[-1][1]:
                            log.log.append(log.Event('Error', 'Hmm should we be getting here... ?'))
                            context_stack.pop()
                        if indent != context_stack[-1][1]:
                            log.log.append(log.Event('Warning', 'INDENT ERROR'))
                        else:
                            if len(context_stack) == 1:
                                self.components.append(new_component)
                            else:
                                context_stack[-2][0].children.append(new_component)
                                new_component.parent = context_stack[-2][0]
                            context_stack[-1] = (new_component, indent)

                    position = self.buffer.get_iter_at_offset(line_start)
                    mark = self.buffer.create_mark(None, position)
                    new_component.locations = [(mark, None, '')]

                    if definition == self.Import:
                        position = self.buffer.get_iter_at_offset(line_end)
                        mark = self.buffer.create_mark(None, position)
                        previous = context_stack[-1][0]
                        previous.locations[0] = (previous.locations[0][0],
                                                 mark,
                                                 previous.locations[0][2])

                statement = ''
            else:
                statement += ' '

    def trim(self):
        """Remove white space at end of lines."""
        lines = self.buffer.get_text(*self.buffer.get_bounds()).splitlines()
        lines = [line.rstrip() for line in lines]
        self.buffer.set_text('\n'.join(lines))

class Delphi_Manager(Language):

    line_comment_start = '//'

    class Class(Source_Component):
        """Data associated with delphi classes."""
        
        def __init__(self, name, base):
            """Initialise the class name."""
            Source_Component.__init__(self, 'Class', name)
            self.base = bases
       
        def tree_row(self):
            """Returns a tuple containing the data used to fill a tree row."""
            markup, location = Source_Component.tree_row(self)
            return (markup + ' (%s)' % (','.join(self.base)), location)

    class Object(Source_Component):
        """Data associated with delphi objects."""
        
        def __init__(self, name, base):
            """Initialise the class name."""
            Source_Component.__init__(self, 'Object', name)
            self.base = bases
       
        def tree_row(self):
            """Returns a tuple containing the data used to fill a tree row."""
            markup, location = Source_Component.tree_row(self)
            return (markup + ' (%s)' % (','.join(self.base)), location)

    class Procedure(Source_Component):
        """Data associated with delphi procedures."""
        
        def __init__(self, name, arguments):
            """Initialise the procedure name."""
            Source_Component.__init__(self, 'Procedure', name)
            self.arguments = arguments

        def tree_row(self):
            """Returns a tuple containing the data used to fill a tree row."""
            markup, location = Source_Component.tree_row(self)
            return (markup + ' (%s)' % (','.join(self.arguments)), location)

    class Function(Source_Component):
        """Data associated with delphi procedures."""
        
        def __init__(self, name, arguments, return_type):
            """Initialise the function name."""
            Source_Component.__init__(self, 'Function', name)
            self.arguments = arguments
            self.return_type = return_type

        def tree_row(self):
            """Returns a tuple containing the data used to fill a tree row."""
            markup, location = Source_Component.tree_row(self)
            return (markup + ' (%s) : %s' % (','.join(self.arguments), self.return_type), location)

    def __init__(self, text_buffer):
        """Set the text buffer associated with this manager."""
        Language.__init__(self, text_buffer)

    def parse(self):
        """Generates the component tree from the buffer associated with this object."""
        text = self.buffer.get_text(*self.buffer.get_bounds())
        text = text.replace('\\\n','  ') # Insert spaces to keep character count consistent

        quoted_string1 = re.compile(r""""[^"]*\"|'[^']*'""")
        quoted_string2 = re.compile(r'""".*"""')
        braces = 0
        quote = 0
        paren1 = 0
        paren2 = 0
        line_start = 0
        line_end = -1
        statement = ''
        context_stack = [(None, 0)]
        previous_component = None
        self.components = []
        for line in text.splitlines():
            line_start = line_end + 1
            line_end = line_start + len(line)
            
            line1 = line.replace('\t', '    ')
            line_text = line1.strip()

            # Ignore blank lines and comment lines
            if not line_text or line_text.startswith('#'):
                continue

            line2 = quoted_string1.sub('', quoted_string2.sub('', line1))
            braces += line2.count('{') - line2.count('}')
            paren1 += line2.count('(') - line2.count(')')
            paren2 += line2.count('[') - line2.count(']')
            quote ^= line2.count('"') % 2

            if not statement:
                indent = len(line1) - len(line1.lstrip())
                
            statement += line_text
            if not (braces or paren1 or paren2 or quote or line[-1] == r"\\"):
                keyword = statement.split()[0]

                if keyword == 'class':
                    definition = self.Class
                elif keyword == 'def':
                    definition = self.Function
                elif keyword == 'import':
                    definition = self.Import
                else:
                    definition = None

                if context_stack[-1][0] and indent <= context_stack[-1][1]:
                    position = self.buffer.get_iter_at_offset(line_start)
                    position.backward_line()
                    mark = self.buffer.create_mark(None, position)

                    while indent < context_stack[-1][1]:
                        previous = context_stack.pop()[0]
                        previous.locations[0] = (previous.locations[0][0],
                                                 mark,
                                                 previous.locations[0][2])
                    if indent != context_stack[-1][1]:
                        log.log.append(log.Event('Warning', 'INDENT ERROR: %s' % (statement)))
                    else:
                        previous = context_stack[-1][0]
                        previous.locations[0] = (previous.locations[0][0],
                                                 mark,
                                                 previous.locations[0][2])

                if definition:
                    name = ' '.join(statement.split()[1:]).strip()
                    pos = name.find('(')
                    if pos != -1:
                        short_name = name[:pos].strip()
                        parameters = name[pos + 1:].split(',')
                        parameters[-1] = parameters[-1][:-2]
                    else:
                        short_name = name[:-1]
                        parameters = []

                    if definition == self.Import:
                        new_component = definition(name)
                    else:
                        new_component = definition(short_name, parameters)

                    if indent == context_stack[-1][1]:
                        if len(context_stack) == 1:
                            self.components.append(new_component)
                        else:
                            context_stack[-2][0].children.append(new_component)
                            new_component.parent = context_stack[-2][0]
                        context_stack[-1] = (new_component, indent)
                    elif indent > context_stack[-1][1]:
                        context_stack[-1][0].children.append(new_component)
                        new_component.parent = context_stack[-1][0]
                        context_stack.append((new_component, indent))
                    else:
                        while indent < context_stack[-1][1]:
                            log.log.append(log.Event('Error', 'Hmm should we be getting here... ?'))
                            context_stack.pop()
                        if indent != context_stack[-1][1]:
                            log.log.append(log.Event('Warning', 'INDENT ERROR'))
                        else:
                            if len(context_stack) == 1:
                                self.components.append(new_component)
                            else:
                                context_stack[-2][0].children.append(new_component)
                                new_component.parent = context_stack[-2][0]
                            context_stack[-1] = (new_component, indent)

                    position = self.buffer.get_iter_at_offset(line_start)
                    mark = self.buffer.create_mark(None, position)
                    new_component.locations = [(mark, None, '')]

                    if definition == self.Import:
                        position = self.buffer.get_iter_at_offset(line_end)
                        mark = self.buffer.create_mark(None, position)
                        previous = context_stack[-1][0]
                        previous.locations[0] = (previous.locations[0][0],
                                                 mark,
                                                 previous.locations[0][2])

                statement = ''
            else:
                statement += ' '

    def trim(self):
        """Remove white space at end of lines."""
        lines = self.buffer.get_text(*self.buffer.get_bounds()).splitlines()
        lines = [line.rstrip() for line in lines]
        self.buffer.set_text('\n'.join(lines))

def add_xml_element(parent, element, position):
    if element.hasAttribute('id'):
        name = "%s id=%s" % (element.tagName, element.getAttribute('id'))
    else:
        name = "%s" % (element.tagName)
    #if element.childNodes and element.firstChild.nodeType == element.TEXT_NODE:
    #    name += ": %s" % (element.firstChild.nodeValue.strip())
    start, end = position.forward_search("<%s" % (element.tagName), 0)
    position.set_offset(start.get_offset())
    mark = position.get_buffer().create_mark(None, position, True)
    position.set_offset(end.get_offset())
    node = (name, mark, [])
    parent[2].append(node)
    for child in element.childNodes:
        if child.nodeType == child.ELEMENT_NODE:
            add_xml_element(node, child, position)

def parse_xml(store, buffer):
    """Parse the text in the supplied buffer to generate a list of definitions."""
    text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())

    try:
        xml_document = xml.dom.minidom.parseString(text)
    except:
        log.log.append(log.Event('Warning', 'XML parse error: %s in %s' % (str(sys.exc_info()), buffer.meta.full_path)))
        return

    position, end = buffer.get_start_iter().forward_search("<%s" % (xml_document.documentElement.tagName), 0)
    mark = buffer.create_mark(None, position, True)
    root = (xml_document.documentElement.tagName, mark, [])
    for child in xml_document.documentElement.childNodes:
        if child.nodeType == child.ELEMENT_NODE:
            add_xml_element(root, child, end)

    # Clear existing data.
    store.clear()
    #for item in root[2]:
    add_to_store(store, root, None)

class Version_Window(gtk.Window):
    """Class containing version information for selected file."""

    def __init__(self, filename, get_versions, retrieve_version, compare = None):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self.set_title('%s Versions' % (filename))
        self.set_default_size(400, 400)
        self.filename = filename
        self.retrieve_version = retrieve_version
        self.compare = compare
        scroll = gtk.ScrolledWindow()
        model = gtk.ListStore(str, str)
        text_cell = gtk.CellRendererText()
        self.list_view = gtk.TreeView(model)
        self.list_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        version_column = gtk.TreeViewColumn('Version', text_cell, text = 0)
        comment_column = gtk.TreeViewColumn('Comment', text_cell, text = 1)
        self.list_view.connect('row-activated', self.get_version)
        self.list_view.connect('key-release-event', self.key_release_cp)
        self.list_view.append_column(version_column)
        self.list_view.append_column(comment_column)
        version_data = get_versions(filename)
        for version in version_data:
            model.append(version)
        scroll.add(self.list_view)
        self.add(scroll)
        self.show_all()

    def get_version(self, treeview, path, view_column):
        model = treeview.get_model()
        version = model.get_value(model.get_iter(path), 0)
        self.retrieve_version(self.filename, version)

    def key_release_cp(self, widget, even):
        self.compare_files()

    def compare_files(self):
        rows = self.list_view.get_selection().get_selected_rows()
        if len(rows[1]) == 2:
            model = rows[0]
            version_1 = model.get_value(model.get_iter(rows[1][0]), 0)
            version_2 = model.get_value(model.get_iter(rows[1][1]), 0)
            command = [cssc_root + 'get', '-p', '-r%s' % (version_1), self.filename]
            process = subprocess.Popen(command, stdout = subprocess.PIPE)
            output = process.stdout
            string_1 = output.read()
            output.close()
            command = [cssc_root + 'get', '-p', '-r%s' % (version_2), self.filename]
            process = subprocess.Popen(command, stdout = subprocess.PIPE)
            output = process.stdout
            string_2 = output.read()
            output.close()
            title = '%s (%s vs %s)' % (os.path.split(self.filename)[1][2:], version_1, version_2)
            self.compare(string_1, string_2, title)

parsers = {'None'   : None,
           'grep'   : parse_grep,
           'make'   : parse_make,
           'Python' : python_visit}

class External_Command(object):
    """Manage execution of external programs."""
    history = []

    def __init__(self, path = None):
        """Execute an external program."""
        self.call_back = None
        self.path = path
        self.dialog = gtk.Dialog(title = 'Command to execute.',
                                 buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                            gtk.STOCK_OK, gtk.RESPONSE_OK))
        self.dialog.set_default_size(400, -1)
        box = gtk.VBox()
        self.entry = gtk.combo_box_entry_new_text()
        self.entry.connect('key-release-event', self.key_release_cb)
        for item in External_Command.history[-10:]:
            self.entry.append_text(item)
        box.pack_start(self.entry)
        self.dialog.get_child().pack_start(box)
        self.dialog.show_all()
        self.dialog.connect('response', self.response_cb)

    def response_cb(self, widget, response):
        """Handles command response actions."""
        results = None
        command = self.entry.get_child().get_text()
        self.dialog.destroy()
        if response == gtk.RESPONSE_OK and command:
            if command not in External_Command.history:
                External_Command.history.append(command)
            if self.path:
                dir, file = os.path.split(self.path)
                if dir:
                    os.chdir(dir)
                command = command.replace('%f', '%s' % (file))
            command = command.replace('sccs ', cssc_root)
            process = subprocess.Popen(command.split(), stdin = subprocess.PIPE, stdout = subprocess.PIPE)
            (output, input) = (process.stdout, process.stdin)
            results = output.read()
            output.close()
        self.call_back(command, results)

    def run(self):
        results = None
        command = None
        response = self.dialog.run()
        if response == gtk.RESPONSE_OK:
            command = self.entry.get_child().get_text()
        self.dialog.destroy()
        if command:
            if command not in External_Command.history:
                External_Command.history.append(command)
            if self.path:
                dir, file = os.path.split(self.path)
                if dir:
                    os.chdir(dir)
                command = command.replace('%f', '%s' % (file))
            command = command.replace('sccs ', cssc_root)
            process = subprocess.Popen(command.split(), stdin = subprocess.PIPE, stdout = subprocess.PIPE)
            (output, input) = (process.stdout, process.stdin)
            results = output.read()
            output.close()
        return (command, results)

    def key_release_cb(self, widget, event):
        """Editing complete close widget."""
        if event.keyval == gtk.keysyms.Return:
            self.dialog.response(gtk.RESPONSE_OK)

    def external_action(self, buffer):
        """Run an external program passing the current buffer as input and replacing
           the buffer with the results"""

        program = self.file_entry.get_text()
        process = subprocess.Popen([program], shell = True,
                                   stdin =subprocess.PIPE, stdout = subprocess.PIPE)
        (output, input) = (process.stdout, process.stdin)
        result_buffer = self.result.get_buffer()
        input.write(result_buffer.get_text(result_buffer.get_start_iter(), result_buffer.get_end_iter()))
        input.close()
        results = output.read()
        output.close()
        buffer.set_text(results)

class Find_Files_Dialog(gtk.Dialog):

    def __init__ (self, path, pattern, include_sub_dirs, match_case):
        gtk.Dialog.__init__(self, title = 'Find Files',
                            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                       gtk.STOCK_OK, gtk.RESPONSE_OK))
        box = gtk.VBox()
        path_box = gtk.HBox()
        path_label = gtk.Label("Path:")
        path_entry = gtk.Entry()
        path_entry.set_text(path)
        path_completion = gtk.EntryCompletion()
        path_completion.set_inline_completion(True)
        path_completion.set_inline_selection(True)
        path_completion.set_popup_completion(True)
        path_entry.set_completion(path_completion)
        path_browse = gtk.Button("Browse")
        path_box.pack_start(path_label, False, False)
        path_box.pack_start(path_entry)
        path_box.pack_start(path_browse, False, False)
        box.pack_start(path_box)
        self.get_child().pack_start(box)
        box.show_all()
        
class Find_Files(object):
    """Manage seaching in files for a string."""
 
    def __init__(self, path = None, pattern = "*", include_sub_dirs = False, match_case = True):
        """Initialise the search dialog."""
        if not path:
            path = os.getcwd()
        dialog = Find_Files_Dialog(path, pattern, include_sub_dirs, match_case)
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            command = self.build_command(dialog)  

class File_Info_Box(gtk.MessageDialog):
    """Display the meta data associated with the buffer."""

    def __init__(self, buffer):
        gtk.MessageDialog.__init__(self, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK)
        self.set_title("File Info")
        text = "Path: <b>%s</b>" % (buffer.meta.full_path)
        if os.path.exists(buffer.meta.full_path):
            stats = os.stat(buffer.meta.full_path)
            text += "\nLast Saved: <b>%s</b>" % (str(datetime.datetime.fromtimestamp(stats.st_mtime)))
        else:
            test += "\n<b>Not Saved</b>"

        if buffer.meta.command:
            text += "\nCommand: <b>%s</b>" % (buffer.meta.command)
        text += "\nFlags:"
        text += " <b>unix</b>" if buffer.meta.is_unix_file else " <b>dos</b>"
        text += " <b>read only</b>" if buffer.meta.read_only else " <b>writable</b>"
        text += " <b>modified</b>" if buffer.get_modified() else " <b>unmodified</b>"
        if buffer.get_language():
            text += "\nLanguage: <b>%s</b>" % (buffer.get_language().get_name())
        else:
            text += "\nLanguage: <b>None</b>"
        text += "\nHash: <b>%s</b>" % (buffer.meta.hash)
        self.set_markup(text)
        self.connect('response', self.response_cb)
        self.show()

    def response_cb(self, dialog, response_id):
        """Processes a response signal, the only response should be a click of the ok button."""
        # Only expected response is a close.
        self.destroy()

class Buffer_Meta(object):
    """Class to manage buffer meta data."""
 
    def __init__(self, path, is_unix_file = False, command = None):
        self.views = set()
        self.full_path = path
        self.hash = None
        self.is_unix_file = is_unix_file
        self.command = command
        self.read_only = False
        self.parser = None
        self.version = None
        self.modified = True
        self.level = 0
        self.completions = {'ANT' : '<a href="acronyms.html#ANT" title="Airborne Navigation Trainer">ANT</a>',
                            'SNT' : '<a href="acronyms.html#SNT" title="Synthetic Navigation Trainer">SNT</a>'}

    def get_path(self):
        return self.__full_path

    def set_path(self, path):
        if path:
            path = os.path.normpath(path).replace('\\', '/')
            self.__full_path = path
            if os.path.isdir(path):
                self.path = path
                self.name = ''
            else:
                self.path, self.name = os.path.split(path)
                if not self.path:
                    self.path = root_path
            self.__full_path = "%s/%s" % (self.path, self.name)
        else:
            self.__full_path = None
            self.name = ''
            self.path = None

    def get_read_status(self):
        status = (('Writable', 'Mutable'), ('Protected', 'Read Only'))
        if self.__full_path and os.path.isfile(self.__full_path):
            file_stat = not os.stat(self.__full_path).st_mode & stat.S_IWRITE
            return status[self.read_only][file_stat]
        else:
            return status[self.read_only][self.read_only]

    def get_read_only(self):
        return self.__read_only

    def set_read_only(self, read_only):
        self.__read_only = read_only
        for view in self.views:
            view.get_child2().get_child().set_editable(not read_only)
            
    def add_completions(self, completions):
        """Append the supplied completions onto the existing dictionary."""
        for key, value in completions.items():
            self.completions[key] = value

    full_path = property(get_path, set_path)
    read_only = property(get_read_only, set_read_only)

class Source_Buffer(gtksourceview2.Buffer):
    """Sub class of the source view buffer to add application meta data."""
    tab_width = None
    tab_character = None
    language_processor = None
    default_parens = [('(', ')'), ('{', '}'), ('[', ']'), ('"', '"'), ("'", "'")]
    save_path = None
    hash_changed = False
    monitor = None
    
    # Directions used in search functionality
    FORWARD = 1
    BACKWARD = -1
    
    __fold = ''

    __gsignals__ = {'title_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,))}

    def __init__(self, path, version = None, is_unix_file = False, command = None):
        gtksourceview2.Buffer.__init__(self)
        self.meta = Buffer_Meta(path, is_unix_file, command)
        self.selection = self.create_tag('selection', foreground = "white", background = "light blue")
        self.create_tag('heading', foreground = "grey", weight = pango.WEIGHT_ULTRALIGHT)
        self.create_tag('hidden', invisible = True)
        self.connect('changed', self.changed_cb)
        self.connect('modified-changed', self.modified_cb)
        if command and command.split()[0] in parsers:
            self.meta.parser = parsers[command.split()[0]]
            self.set_text('<command: %s (ctrl-r to rerun)>' % (command))
        elif path:
            self.load_file(path, version)

    def get_tab(self):
        """Returns the tab character used in the current buffer."""
        tab = config.tab_character
        if self.tab_character:
            tab = self.tab_character
        if tab != '\t':
            tab = tab * self.get_tab_width()
        return tab

    def get_tab_width(self):
        """Returns the tab width for this buffer."""
        result = config.tab_width
        if self.tab_width:
            result = tab_width
        elif hasattr(self.get_language(), 'tab_width'):
            result = self.get_language().tab_width
        return result

    def set_selection(self, offset, length):
        """Sets the selection based on the character offset of the displayed string."""
        start = self.get_start_iter()
        if self.is_folded():
            hidden = self.get_tag_table().lookup('hidden')
            if start.has_tag(hidden):
                start.forward_to_tag_toggle(hidden)
            while 1:
                next = start.copy()
                next.forward_to_tag_toggle(hidden)
                if (next.get_offset() - start.get_offset()) > offset:
                    start.forward_chars(offset)
                    break
                else:
                    offset -= (next.get_offset() - start.get_offset()) 
                    start = next.copy()
                    start.forward_to_tag_toggle(hidden)
            end = start.copy()
            while 1:
                next = end.copy()
                next.forward_to_tag_toggle(hidden)
                if (next.get_offset() - end.get_offset()) > length:
                    end.forward_chars(length)
                    break
                else:
                    length -= (next.get_offset() - end.get_offset()) 
                    end = next.copy()
                    end.forward_to_tag_toggle(hidden)
        else:
            start.forward_chars(offset)
            end = start.copy()
            end.forward_chars(length)

        self.select_range(start, end)

        return end            

    def hide_selection(self):
        """Hides the selected text keeping the first line to provide context."""
        selection = self.get_selection_bounds()
        if selection:
            start, end = selection
            if end.get_line() - start.get_line() > 1:
                start = self.get_iter_at_line(start.get_line())
                hidden_start = start.copy()
                hidden_start.forward_line()
                end_first_line = start.copy()
                end_first_line.forward_to_line_end()
                self.apply_tag_by_name('heading', start, end_first_line)            

                if not end.ends_line():
                    end.forward_to_line_end()
                self.apply_tag_by_name('hidden', hidden_start, end)
            else:
                self.apply_tag_by_name('hidden', start, end)
        else:
            cursor = self.get_iter_at_mark(self.get_insert())
            if cursor.has_tag(self.tag_table.lookup('hidden')) \
                or cursor.has_tag(self.tag_table.lookup('heading')):
                self.show()
        self.update_title()

    def show(self):
        """Shows hidden text at insertion point."""
        cursor = self.get_iter_at_mark(self.get_insert())
        if cursor:
            end = cursor.copy()
            end.forward_visible_lines(1)
            start = self.get_iter_at_line(cursor.get_line())
            self.remove_tag_by_name('heading', start, end)
            self.remove_tag_by_name('hidden', start, end)
        self.update_title()

    def show_all(self):
        """Shows all text."""
        self.remove_tag_by_name('heading', *self.get_bounds())
        self.remove_tag_by_name('hidden', *self.get_bounds())
        if self.__fold:
            del self.__fold
        self.update_title()

    def find_next(self, pattern, is_re, start=None):
        """Return a tuple containing start and end iterators for the next match of the pattern."""
        if not start:
            start = self.get_start_iter()
        self.place_cursor(start)
        string = self.get_text(start, self.get_end_iter())
        end = start.copy()
        if is_re:
            match = pattern.search(string)
            if match:
                start.forward_chars(match.start())
                end.forward_chars(match.end())
            else:
                return None
        else:
            position = string.find(pattern)
            if position != -1:
                start.forward_chars(position)
                end.forward_chars(position + len(pattern))
            else:
                return None
        return start, end

    def get_matches(self, pattern, is_re=False, lines_before=0, lines_after=0):
        """Return a list of tuples for the matched text."""
        matches = []
        if is_re:
            pattern = re.compile(pattern)
        match = self.find_next(pattern, is_re)
        while match:
            match[0].backward_lines(lines_before)
            match[1].forward_lines(lines_after)
            matches.append(match)
            match = self.find_next(pattern, is_re, match[1].copy())
        
        return matches

    def show_matches(self, pattern, is_re=False, lines_before=0, lines_after=0):
        """Hide text that doesn't match the pattern."""
        matches = self.get_matches(pattern, is_re, lines_before, lines_after)
        if matches:
            self.apply_tag_by_name('hidden', self.get_start_iter(), matches[0][0])     
            for index in range(1, len(matches)):
                self.apply_tag_by_name('hidden', matches[index - 1][1], matches[index][0])     
            self.apply_tag_by_name('hidden', matches[-1][1], self.get_end_iter())                 

    def hide_matches(self, pattern, is_re=False, lines_before=0, lines_after=0):
        """Hide text (and context lines) that matches pattern."""
        matches = self.get_matches(pattern, is_re, lines_before, lines_after)
        for start, end in matches:
            self.apply_tag_by_name('hidden', start, end)     

    def can_fold(self):
        """Returns a flag indicating whether the buffer can be folded at the cursor."""
        iter = self.get_iter_at_mark(self.get_insert())
        tags = iter.get_tags()
        scope_tags = []
        if tags:
            scope_tags = [tag for tag in tags if tag.get_property('name')
                          and tag.get_property('name').startswith('scope:')
                          and len(tag.get_property('name')) > len(self.__fold)]
                          
        return bool(scope_tags)

    def is_folded(self):
        """Returns a flag indicating whether the buffer contents are currently folded."""
        return bool(self.__fold)

    def fold(self):
        """Hides outer most visible scope."""
        iter = self.get_iter_at_mark(self.get_insert())
        tags = iter.get_tags()
        if tags:
            scope_tags = [tag for tag in tags if tag.get_property('name')
                          and tag.get_property('name').startswith('scope:')
                          and len(tag.get_property('name')) > len('scope:' + self.__fold)]
            item = scope_tags[0] if scope_tags else None
            for tag in scope_tags:
                if len(tag.get_property('name')) < len(item.get_property('name')):
                    item = tag

        if item:
            start = self.get_start_iter()
            if not start.begins_tag(item):
                end = start.copy()
                end.forward_to_tag_toggle(item)
            while not start.is_end():
                self.apply_tag_by_name('hidden', start, end)
                start = end
                start.forward_to_tag_toggle(item)
                end = start.copy()
                end.forward_to_tag_toggle(item)
            self.__fold = item.get_property('name').replace('scope:', '')
        self.update_title()

    def unfold(self):
        """Show next outer scope."""
        current_name = 'scope:' + self.__fold
        iter = self.get_iter_at_mark(self.get_insert())
        line = iter.get_line() + 1
        column = iter.get_line_offset()
        tags = iter.get_tags()
        if tags:
            scope_tags = [tag for tag in tags if tag.get_property('name')
                          and tag.get_property('name').startswith('scope:')
                          and len(tag.get_property('name')) < len(current_name)]
            if not scope_tags:
                self.show_all()
                return
                
            item = scope_tags[0]
            for tag in scope_tags:
                if (tag.get_property('name').startswith('scope:')
                    and len(tag.get_property('name')) > len(item.get_property('name'))):
                    item = tag

        if item:
            self.remove_tag_by_name('hidden', *self.get_bounds())
            start = self.get_start_iter()
            if not start.begins_tag(item):
                end = start.copy()
                end.forward_to_tag_toggle(item)
            while not start.is_end():
                self.apply_tag_by_name('hidden', start, end)
                start = end
                start.forward_to_tag_toggle(item)
                end = start.copy()
                end.forward_to_tag_toggle(item)
            self.__fold = item.get_property('name').replace('scope:', '')
        self.update_title()

    def max_fold(self):
        """Hides outer most visible scope."""
        self.remove_tag_by_name('hidden', *self.get_bounds())
        iter = self.get_iter_at_mark(self.get_insert())
        tags = iter.get_tags()
        item = None
        if tags:
            scope_tags = [tag for tag in tags if tag.get_property('name')
                          and tag.get_property('name').startswith('scope:')]
            item = scope_tags[0]
            for tag in scope_tags:
                if (tag.get_property('name').startswith('scope:')
                    and len(tag.get_property('name')) > len(item.get_property('name'))):
                    item = tag
        if item:
            start = self.get_start_iter()
            if not start.begins_tag(item):
                end = start.copy()
                end.forward_to_tag_toggle(item)
            while not start.is_end():
                self.apply_tag_by_name('hidden', start, end)
                start = end
                start.forward_to_tag_toggle(item)
                end = start.copy()
                end.forward_to_tag_toggle(item)
            self.__fold = item.get_property('name').replace('scope:', '')
        self.update_title()

    def view_current_item(self):
        """Hides all but the inner most item."""
        iter = self.get_iter_at_mark(self.get_insert())
        line = iter.get_line() + 1
        column = iter.get_line_offset()
        tags = iter.get_tags()
        if tags:
            named_tags = [tag for tag in tags if tag.get_property('name')]
            item = named_tags[0]
            for tag in named_tags:
                if (tag.get_property('name').startswith('scope:')
                    and len(tag.get_property('name')) > len(item.get_property('name'))):
                    item = tag

        if item:
            start = self.get_start_iter()
            if not start.begins_tag(item):
                end = start.copy()
                end.forward_to_tag_toggle(item)
            while not start.is_end():
                self.apply_tag_by_name('hidden', start, end)
                start = end
                start.forward_to_tag_toggle(item)
                end = start.copy()
                end.forward_to_tag_toggle(item)
            self.__fold = item.get_property('name').replace('scope:', '')
        self.update_title()

    def set_language(self, language):
        """Include the local specialisation of buffer parsing."""
        gtksourceview2.Buffer.set_language(self, language)
        if language.get_name() in parsers:
            self.meta.parser = parsers[language.get_name()]
        if language.get_name() == 'Python':
            manager = Python_Manager(self)
            manager.parse()
            self.language_processor = manager

    def get_word(self):
        """Returns the current selction or word at cursor if no selection."""
        selection = self.get_selection_bounds()
        if selection:
            start, end = selection 
        else:
            cursor = self.get_iter_at_mark(self.get_insert())
            end = cursor.copy()
            end.forward_word_end()
            if cursor.inside_word():
                start = cursor.copy()
            else:
                start = end.copy()
            if not start.starts_word():
                start.backward_word_start()
            
        if start.compare(end) == 0:
            start, end = None, None
        return start, end        

    def capitalise(self):
        """Capitalise the selection or word at cursor position."""
        start, end = self.get_word()
        if start:
            string = self.get_text(start, end).capitalize()
            self.begin_user_action()
            self.delete(start, end)
            self.insert(start, string)
            self.place_cursor(start)
            self.end_user_action()

    def lower(self):
        """Change the selection or word at cursor position to lower case."""
        start, end = self.get_word()
        if start:
            string = self.get_text(start, end).lower()
            self.begin_user_action()
            self.delete(start, end)
            self.insert(start, string)
            self.place_cursor(start)
            self.end_user_action()

    def upper(self):
        """Change the selection or word at cursor position to upper case."""
        start, end = self.get_word()
        if start:
            string = self.get_text(start, end).upper()
            self.begin_user_action()
            self.delete(start, end)
            self.insert(start, string)
            self.place_cursor(start)
            self.end_user_action()

    def changed_cb(self, buffer):
        """Ensures the cursor is visible in the view with the focus."""
        focused_view = None
        for view in self.meta.views:
            if view.is_focus():
                 focused_view = view
        if focused_view:
            focused_view.scroll_to_mark(mark, 0.0, use_align=True, xalign=1.0, yalign=0.25)

    def modified_cb(self, buffer):
        """Handles a change to the modification state, updating the title if necessary."""
        if self.get_modified() != self.meta.modified:
            title = self.get_title(markup = True)
            for view in self.meta.views:
                view.set_title(title)
                view.parse()

    def forward_to_parentesis(self):
        """Move the cursor forwards to the matching patenthesis."""
        cursor = self.matching_paren(self.FORWARD)
        if cursor:
            self.place_cursor(cursor)

    def back_to_parentesis(self):
        """Move the cursor backwards to the matching patenthesis."""
        cursor = self.matching_paren(self.BACKWARD)
        if cursor:
            self.place_cursor(cursor)
 
    def get_title(self, full=False, markup=False):
        """Returns a string representing the title of this buffer."""
        full_name = self.meta.command if self.meta.command else self.meta.full_path
        short_name = self.meta.command.split()[0] if self.meta.command else self.meta.name
        title = full_name if full else short_name

        if full and self.__fold:
            title += ':%s' % (self.__fold)

        if not title:
            title = 'New'
        if self.get_modified():
            title += '*'
        if self.hash_changed:
            if markup:
                title = '<span foreground="red">' + title + '</span>'
            else:
                title = '*' + title
        return title

    def update_title(self):
        """Updates the title for views showing this buffer."""
        for view in self.meta.views:
            view.set_title(self.get_title(markup = True))

    def insert_completion(self):
        """Replace the curent world with a completion."""
        selection = self.get_selection_bounds()
        if selection:
             (start, end) = (selection[0], selection[1])
        else:
            start = self.get_iter_at_mark(self.get_insert())
            if not start.starts_word():
                start.backward_word_start()
            end = self.get_iter_at_mark(self.get_insert())
            if not end.ends_word():
                end.forward_word_end()

        word = self.get_text(start, end)
        if word in self.meta.completions:
            completion = self.meta.completions[word]
            self.delete(start, end)
            self.insert(start, completion)

    def run_command(self):
        """Runs the specified command."""
        os.chdir(self.meta.path)
        command = self.meta.command.replace('sccs ', cssc_root)
        process = subprocess.Popen(command.split(), stdin = subprocess.PIPE, stdout = subprocess.PIPE,
                                   stderr = subprocess.STDOUT)
        (output, input) = (process.stdout, process.stdin)
        results = output.read()
        output.close()
        self.set_text(results)

    def determine_language(self):
        """Determine the language from the file name."""
        language = None #language_manager.get_language('xml')
        name = self.meta.name
        for pattern in language_manager.globs:
            if fnmatch.fnmatchcase(name, pattern):
                language = language_manager.get_language(language_manager.globs[pattern])
        if language:
            self.set_language(language)

    def import_text(self, text):
        """Set the text of the buffer."""
        encoding = locale.getpreferredencoding()
        text = text.replace('\0', '') # strip out invalid characters
        try:
            self.set_text(unicode(text, encoding))
        except UnicodeDecodeError:
            self.set_text(text)

    def load_file(self, filename, version = None):
        """Loads the specified file into this buffer."""
        self.meta.full_path = filename
        version_file = None
        if filename.startswith('http:'):
            file = urllib2.urlopen(filename)
            self.meta.read_only = True
            name = os.path.basename(filename)
        elif os.path.isfile(filename):
            manager.add_item('file:' + filename)
            self.meta.read_only = not os.stat(filename).st_mode & stat.S_IWRITE
            extension = os.path.splitext(filename)[1]
            if extension == ".gz":
                file = gzip.open(filename, 'rb')
                name = os.path.splitext(filename)[0]
            elif extension == ".bz2":
                file = bz2.BZ2File(filename, 'Ur')
                name = os.path.splitext(filename)[0]
            else:
                name = os.path.basename(filename)
                # if the file name starts with 's.' assume it is a sccs file
                if name.startswith('s.'):
                    if version:
                        command = [cssc_root + 'get', '-p', '-r%s' % (version),filename]
                    else:
                        command = [cssc_root + 'get', '-p', filename]
                    process = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    file = process.stdout
                    version_file = process.stderr
                    self.meta.read_only = True
                else:
                    file = open(filename, 'Ur')
        else:
            file = None

        if file:
            content = file.read()
            self.import_text(content)
            file.close()
            self.meta.hash = hashlib.sha1(content).hexdigest()
            self.hash_changed = False
            self.meta.name = name
            self.determine_language()
            if version_file:
                version = version_file.read().split()[0]
                self.meta.name += ' V%s' % (version)
                self.meta.version = version
            if extension != ".gz" and extension != ".bz2":
                if file.newlines == '\n':
                    self.meta.is_unix_file = True
            self.set_modified(False)
            self.meta.modified = False
            self.monitor = file_monitor.Monitor(filename, self.file_changed_cb)
        else:
            self.set_text("Couldn't open file %s" % (filename))
 
    def file_changed_cb(self, monitor):
        """Callback called when file associated with this buffer has changed."""
        self.hash_changed = False
        if self.meta.full_path and os.path.exists(self.meta.full_path):
            on_disk = open(self.meta.full_path)
            hash = hashlib.sha1(on_disk.read()).hexdigest()
            on_disk.close()
            self.hash_changed = hash != self.meta.hash
        self.update_title()
        return True

    def save(self, filename, open_function, mode):
        """Save the contents of the buffer to the given filename."""
        self.meta.full_path = filename
        self.__class__.save_path = os.path.dirname(filename) + '/'
        string = self.get_text(self.get_start_iter(), self.get_end_iter())
        destination = open_function(filename, mode)
        destination.write(string)
        destination.close()
        self.meta.hash = hashlib.sha1(string).hexdigest()
        self.set_modified(False)
        title = self.get_title(markup = True)
        for view in self.meta.views:
            view.set_title(title)

    def save_text(self, filename):
        """Save the contents of the results buffer to the selected file"""
        if self.meta.is_unix_file:
            mode = 'wb'
        else:
            mode = 'w'
        self.save(filename, open, mode)

    def save_zip(self, filename):
        """Save the contents of the results buffer to the selected file"""
        self.save(filename, gzip.open, 'wb')

    def save_bzip(self, filename):
        """Save the contents of the results buffer to the selected file"""
        self.save(filename, BZ2File, 'w')

    def save_file_as(self, reason=None):
        """Choose destination file for saving to."""
        dialog = gtk.FileChooserDialog("Save As...", None, gtk.FILE_CHOOSER_ACTION_SAVE,
                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        if reason:
            reason_text = gtk.Label(reason)
            reason_text.show_all()
            dialog.vbox.pack_start(reason_text, False)
            dialog.vbox.reorder_child(reason_text, 0)
        if self.save_path:
            dialog.set_current_folder(self.save_path)
        filename = None
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            filename =  dialog.get_filename()
        elif response == gtk.RESPONSE_CANCEL:
            [view.get_toplevel().update_status('save cancelled') for view in self.meta.views]
        dialog.destroy()
        if filename:
            if os.path.splitext(filename)[1] == ".gz":
                self.save_zip(filename)
            elif os.path.splitext(filename)[1] == ".bz2":
                self.save_bzip(filename)
            else:
                self.save_text(filename)
            return True
        return False

    def save_file(self):
        """Choose destination file for saving to."""
        hash_change = False
        if self.meta.full_path and os.path.isfile(self.meta.full_path):
            on_disk = open(self.meta.full_path)
            hash = hashlib.sha1(on_disk.read()).hexdigest()
            on_disk.close()
            hash_change = hash != self.meta.hash
        if hash_change or self.meta.read_only and os.path.isfile(self.meta.full_path):
            path = os.path.dirname(self.meta.full_path)
            if path:
                self.__class__.save_path = path
            if self.meta.read_only:
                reason = '%s is read only' % (self.meta.full_path)
            else:
                reason = '%s has changed since loading' % (self.meta.full_path)
            result = self.save_file_as(reason)
        elif not self.meta.full_path:
            result = self.save_file_as('Path not yet set for file')        
        elif 'temp' in self.meta.full_path:
            result = self.save_file_as('%s appears to be temporary location' % (self.meta.full_path))        
        elif not os.path.isfile(self.meta.full_path):
            result = self.save_file_as('%s is not a normal file' % (self.meta.full_path))        
        else:
            filename = self.meta.full_path
            if os.path.splitext(filename)[1] == ".gz":
                self.save_zip(filename)
            elif os.path.splitext(filename)[1] == ".bz2":
                self.save_bzip(filename)
            else:
                self.save_text(filename)
            result = True
        return result

    def remove_view(self, view):
        """Removes the selected view of this buffer,
           saving the buffer if it's the only view.
        """
        close = True
        if view in self.meta.views:
            if len(self.meta.views) < 2 and self.get_modified() and not self.meta.command:
                if self.confirm_close():
                    self.meta.views.remove(view)
                else:
                    close = False
        else:
            Status("Warning: attempt to remove unlinked view!.")
        if close and view.context:
            contexts[view.context].remove_buffer(self)
        return close

    def confirm_close(self):
        """Close the page containing the selected widget."""
        close = False
        dialog = gtk.Dialog(title = 'Unsaved data',
                            buttons = ((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_NO, gtk.RESPONSE_NO,
                                        gtk.STOCK_YES, gtk.RESPONSE_YES)))
        label = gtk.Label('Save %s before close' % (self.meta.name))
        label.show_all()
        dialog.get_child().pack_start(label)
        response = dialog.run()
        dialog.destroy()
        if response == gtk.RESPONSE_YES:
            close = self.save_file()
        elif response == gtk.RESPONSE_NO:
            close = True
        return close

    def toggle_highlight_syntax(self):
        """Toggle the state of the highlight syntax flag."""
        self.set_highlight_syntax(not self.get_highlight_syntax())

    def indent(self):
        """Indent the selected code."""
        selection = self.get_selection_bounds()
        if selection:
            start, end = selection
        else:
            start = self.get_iter_at_mark(self.get_insert())
            end = start.copy()
        start = self.get_iter_at_line(start.get_line())
        tab = self.get_tab()
        if end.ends_line() or end.starts_line():
            end = end.get_line()
        else:
            end = end.get_line() + 1
        last_line = self.get_end_iter().get_line()
        if end > last_line:
            end = last_line
        self.begin_user_action()
        while start.get_line() < end:
            if not start.ends_line():
                self.insert(start, tab)
            start.forward_visible_line()
        self.end_user_action()
    
    def match(self, char, data):
        """Find the first non-space character."""
        if char != ' ':
            return True
        else:
            return False

    def matching_paren(self, direction=None, ignore_encapsulated=True):
        """Return an iterator at the position of the matching parenthesis."""
        if self.language_processor:
            parens = self.language_processor.parens
        else:
            parens = self.default_parens
        
        cursor = self.get_iter_at_mark(self.get_insert())
        pattern = None
        # Search either side of the cursor.
        found_open = found_close = False
        for opening_pattern, closing_pattern in parens:
            start_iter = cursor.copy()
            start_iter.backward_chars(len(opening_pattern))
            end_iter = cursor.copy()
            end_iter.forward_chars(len(opening_pattern))
            text = self.get_text(start_iter, end_iter)
            if text.find(opening_pattern) != -1:
                found_open = True
                cursor.backward_chars(len(opening_pattern))
                cursor.forward_chars(text.find(opening_pattern))

            if not found_open or direction == self.BACKWARD:
                start_iter = cursor.copy()
                start_iter.backward_chars(len(closing_pattern))
                end_iter = cursor.copy()
                end_iter.forward_chars(len(closing_pattern))
                text = self.get_text(start_iter, end_iter)
                if text.find(closing_pattern) != -1:
                    found_close = True
                    cursor.forward_chars(text.find(closing_pattern))
            
            if direction == self.FORWARD and found_open:
                break
            elif direction == self.BACKWARD and found_close:
                break
            elif direction == None and opening_pattern != closing_pattern:
                if found_open:
                    direction = self.FORWARD
                    break
                elif found_close:
                    direction = self.BACKWARD
                    break

        if not (found_open or found_close):
            return None

        if direction == self.FORWARD:
            text = self.get_text(cursor, self.get_end_iter())
            position = 0
            done = False
            while not done:
                position = text.find(closing_pattern, position) + len(closing_pattern)
                if position == -1:
                    break
                if opening_pattern == closing_pattern:
                    done = text[:position].count(opening_pattern) == 2
                else:
                    done = text[:position].count(opening_pattern) == text[:position].count(closing_pattern)
            cursor.forward_chars(position)
        else:
            text = self.get_text(self.get_start_iter(), cursor)
            position = len(text)
            done = False
            while not done:
                position = text.rfind(opening_pattern, 0, position)
                if position == -1:
                    break
                if opening_pattern == closing_pattern:
                    done = text[position:].count(opening_pattern) == 2
                else:
                    done = text[position:].count(opening_pattern) == text[position:].count(closing_pattern)
            cursor.backward_chars(len(text) - position)

        if position == -1:
            cursor = None
        return cursor

    def outdent(self):
        """Remove one level of indent from the selected code."""
        selection = self.get_selection_bounds()
        if selection:
            start, end = selection
        else:
            start = self.get_iter_at_mark(self.get_insert())
            end = start.copy()
        start = self.get_iter_at_line(start.get_line())
        tab_width = self.get_tab_width()
        if end.ends_line() or end.starts_line():
            end = end.get_line()
        else:
            end = end.get_line() + 1
        self.begin_user_action()
        while start.get_line() < end:
            tab_end = start.copy()
            if str(start.get_char()) == '\t':
                tab_end.forward_char()
            else:
                tab_end.forward_chars(tab_width)
                text = self.get_text(start, tab_end)
                back = len(text.lstrip(' '))
                tab_end.backward_chars(back)
            self.delete(start, tab_end)
            start.forward_visible_line()
        self.end_user_action()

class View_Meta(object):
    """Class to manage view meta data."""
    context = None

    def __init__(self, font = None):
        if font:
            self.font = font
        else:
            self.font = config.font
        self.prior_locations = []
        self.forward_locations = []
        self.read_only = False
        self.show_line_numbers = False
        self.parser = None

    def get_font(self):
        return self.__font

    def set_font(self, font):
        if font:
            self.__font = pango.FontDescription(font)

    font = property(get_font, set_font)

    def get_context(self):
        """Return the context of this view."""
        if self.context:
            return self.context.get_name()
        else:
            return '<span foreground="orange"><b>Unmanaged</b></span>'

    def bookmark(self, location):
         """Stores the given location as a bookmark."""
         self.prior_locations.append(location)
         self.forward_locations = []

    def can_go_back(self):
        return self.prior_locations

    def go_back(self, current_location):
        """Move to the last bookmark, adding the current location to the stack."""
        self.forward_locations.append(current_location)
        return self.prior_locations.pop()

    def can_go_forward(self):
        return self.forward_locations

    def go_forward(self, current_location):
        """Move to the next bookmark, adding the current location to the stack."""
        self.prior_locations.append(current_location)
        return self.forward_locations.pop()

class Tree_View(gtk.ScrolledWindow):
    """Tree widget containing code structure."""

    def __init__(self, buffer):
        gtk.ScrolledWindow.__init__(self)
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.sort = True
        child_store = gtk.TreeStore(str, gtk.TextMark, gtk.gdk.Pixbuf)
        store = gtk.TreeModelSort(child_store)
        self.view = gtk.TreeView(store)
        self.view.connect('row-activated', self.row_activated_cb)
        self.view.get_selection().set_mode(gtk.SELECTION_BROWSE)
        self.view.get_selection().connect('changed', self.selection_changed_cb)

        column = gtk.TreeViewColumn('Element') #, image_cell, pixbuf = 2)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.view.append_column(column)
        
        image_cell = gtk.CellRendererPixbuf()
        column.pack_start(image_cell, False)

        text_cell = gtk.CellRendererText()
        column.pack_start(text_cell, True)

        column.set_attributes(image_cell, pixbuf = 2)
        column.set_attributes(text_cell, markup = 0)

        self.view.connect('button-press-event', self.button_cb)
        self.connect('popup-menu', self.context_menu_cb)
        self.set_sort()
        self.add(self.view)

    def selection_changed_cb(self, selection):
        """Jump to the position associtated with the selected row."""
        model, iter = selection.get_selected()
        if iter:
            mark = model.get_value(iter, 1)
            self.parent.goto_mark(mark)
            if config.auto_fold:
                text_buffer = self.parent.get_buffer()
                position = text_buffer.get_iter_at_mark(mark)
                tag = text_buffer.get_tag_table().lookup('hidden')
                if tag and position.has_tag(tag):
                    self.parent.get_buffer().max_fold()

    def row_activated_cb(self, treeview, path, column):
        """Jump to the position associtated with the selected row."""
        iter = treeview.get_model().get_iter(path)
        mark = treeview.get_model().get_value(iter, 1)
        self.parent.goto_mark(mark)
        if config.auto_fold:
            self.parent.get_buffer().max_fold()
        
    def button_cb(self, widget, event):
        """If right button display context menu, alse perform standard event processing."""
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
            self.context_menu_cb(widget)

    def context_menu_cb(self, event):
        """Displays the context menu for the tree view."""
        menu = []
        if self.sort:
            menu.append(('oxxSort', self.sort_cb))
        else:
            menu.append(('oxoSort', self.sort_cb))
        menu.append(('---', '---'))
        menu.extend([('Expand All', self.expand_all_cb),
                     ('Collapse All', self.collapse_all_cb)])

        menu = build_menu(menu)
        menu.popup(None, None, None, 1, gtk.get_current_event_time())

    def jump_to_cb(self, item):
        """Move the text buffer tot he possition associated with the selected item."""
        selection = self.view.get_selection()
        model, iter = selection.get_selected()
        if iter:
            mark = model.get_value(iter, 1)
            self.parent.goto_mark(mark)

    def expand_all_cb(self, item):
        """Expands all task in the tree to show all children and sub children etc."""
        self.view.expand_all()

    def collapse_all_cb(self, item):
        """Collapses all task in the tree to show only top level tasks."""
        self.view.collapse_all()

    def sort_cb(self, item):
        """Inverts the sort flag and updates the list."""
        self.sort = not self.sort
        self.set_sort()

    def set_sort(self):
        """Sorts the tree based on the setting of the sort flag."""
        model = self.view.get_model()
        if self.sort:
            model.set_default_sort_func(self.sort_by_name)
        else:
            model.set_default_sort_func(self.unsorted)

    def sort_by_name(self, treemodel, iter1, iter2):
        """Sort function used returning a non case sensitive sort of the tree elements."""
        item1 = treemodel.get_value(iter1, 0)
        item2 = treemodel.get_value(iter2, 0)
        if item1 and item2:
            return cmp(item1.lower(), item2.lower())
        else:
            return 0
    
    def unsorted(self, treemodel, iter1, iter2):
        """Sort function returning the order based on position in code."""
        item1 = treemodel.get_value(iter1, 1)
        item2 = treemodel.get_value(iter2, 1)
        return cmp(item2, item1)

class Tree_Source_View(gtk.HPaned):
    """Widget containing a source view area and source tree."""
    context = None
    
    def __init__(self, buffer):
        """Create a paned widget with the tree and source views."""
        gtk.HPaned.__init__(self)
        tree = Tree_View(buffer)
        source_window = gtk.ScrolledWindow()
        view = gtksourceview2.View(buffer)
        view.set_tab_width(buffer.get_tab_width())
        view.set_smart_home_end(True)
        view.set_editable(not buffer.meta.read_only)
        self.meta = View_Meta()
        self.meta.parser = buffer.meta.parser
        buffer.meta.views.add(self)
        view.modify_font(self.meta.font)
        view.set_auto_indent(True)
        #view.connect_after('move-cursor', self.cursor_cb)
        buffer.connect_after('mark-set', self.mark_cb)
        buffer.connect_after('changed', self.changed_cb)
        source_window.add(view)
        self.connect('delete-event', self.delete)
        self.connect('button-release-event', self.button_release)
        self.connect('focus-in-event', self.focus_in_cb)
        view.connect('insert-at-cursor', self.insert_cb)
        view.connect('move-cursor', self.move_cursor_cb)
        view.connect('populate-popup', self.popup_cb)
        view.connect('query-tooltip', self.query_tooltip_cb)
        view.set_property('has-tooltip', True)
        self.pack1(tree, False, True)
        self.pack2(source_window, True, False)
        self.set_position(0)
        self.show_all()

    def popup_cb(self, textview, menu):
        """Add custom entries to the popup menu."""
        window = self.get_toplevel()
        custom_menu = [('Bookmark', window.bookmark, 'redo'),
                       ('Back', window.back, 'back'),
                       ('Forward', window.forward, 'forward'),
                       ('New view', window.new_view_cb),
                       ('---', '---'),
                       ('Hide Selection', window.hide_cb),
                       ('Show All', window.show_all_cb),
                       ('Full Fold', window.view_item_cb)]
        if self.get_buffer().can_fold():
            custom_menu.append(('Fold', window.fold_cb))
        if self.get_buffer().is_folded():
            custom_menu.append(('Unfold', window.unfold_cb))
        new_item = gtk.MenuItem('View')
        new_item.set_submenu(build_menu(custom_menu))
        menu.append(gtk.SeparatorMenuItem())
        menu.append(new_item)
        if self.get_buffer().language_processor:
            menu_definition =  self.get_buffer().language_processor.menu
            if menu_definition:
                definition = [(item[0], item[1], self) for item in menu_definition]
                new_item = gtk.MenuItem(self.get_buffer().get_language().get_name())
                new_item.set_submenu(build_menu(definition))
                menu.append(new_item)        
        menu.show_all()

    def query_tooltip_cb(self, view, x, y, keyboard_mode, tooltip):
        """Determine what text to show in the tool tip."""
        x, y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
        iter = view.get_iter_at_location(x, y)
        tags = iter.get_tags()
        if tags:
            tags.reverse()
            named_tags = [tag.get_property('name') for tag in tags if tag.get_property('name')]
            text = ''
            for tag in named_tags:
                if tag.startswith('scope:') and len(tag) > len(text):
                    text += tag

            text = text.replace('scope:', '')
            if text:
                #label = gtk.Label(text)
                #label.set_markup(text)
                #tooltip.set_custom(label)
                tooltip.set_markup(text)
                return True
        return False

    def focus_in_cb(self, widget, event):
        """Give the source window the focus."""
        self.get_view().grab_focus()

    def move_cursor_cb(self, textview, step_size, count, extend_selection):
        """Add cursor movement to the auto macro."""
        if not extend_selection:
            event = Cursor_Movement(step_size, count, self.get_buffer())
            self.get_toplevel().auto_macro.add_event(event)

    def insert_cb(self, textview, text):
        """Add the text to the auto macro."""
        event = Insert(text, self.get_buffer())
        self.get_toplevel().auto_macro.add_event(event)

    def parse(self):
        """Parse the buffer, creating the tree view and tags."""
        buffer = self.get_buffer()
        store = self.get_child1().view.get_model().get_model()
        if buffer.language_processor:
            buffer.language_processor.parse()
            buffer.language_processor.populate_store(store)
            buffer.language_processor.apply_tags()
            
        elif buffer.get_language():
            language = buffer.get_language().get_name()
            if language == 'Python':
                manager = Python_Manager(buffer)
                manager.parse()
                manager.populate_store(store)
                manager.apply_tags()
                buffer.language_processor = manager
            elif language == 'C':
                manager = C_Manager(buffer)
                buffer.language_processor = manager
                parse_c(store, buffer)
            elif language == 'C++':
                manager = Cpp_Manager(buffer)
                buffer.language_processor = manager
                parse_cpp(store, buffer)
            elif language == 'Ada':
                parse_ada(store, buffer)
            elif language == 'XML':
                parse_xml(store, buffer)

    def get_buffer(self):
        """Returns the buffer associated with this view."""
        return self.get_child2().get_child().get_buffer()

    def get_view(self):
        """Returns the source view widget associated with this widget."""
        view_pane = self.get_child2()
        if view_pane:
            return view_pane.get_child() 
        return None

    def set_title(self, title):
        """Update the title of the current window."""
        toplevel = self.get_toplevel()
        container = toplevel.tab_widget
        container.get_tab_label(self).get_children()[0].set_markup(title)
        container.get_tab_label(self).get_children()[0].set_use_markup(True)
        container.get_tab_label(self).get_children()[0].queue_draw()
        active_page = container.get_nth_page(container.get_current_page())
        #if self == active_page:
            #toplevel.set_title("Editor: %s" % (self.get_buffer().get_title(full=True)))
            #toplevel.file_status.set_label(self.get_buffer().meta.get_read_status())

    def button_release(self, widget, event):
        if event.button == 2:
            pass

    def delete(self, widget, event):
        """Remove the widget from the list for it's associated buffer."""
        self.close()

    def close(self):
        """Close this view, if this is the last view of this file close the buffer."""
        buffer = self.get_buffer()
        closed = False
        if buffer.remove_view(self):
            if self.context:
                contexts[self.context].remove_view(self)
            self.destroy()
            closed = True
        return closed

    def cursor_cb(self, textview, step_size, count, extend_selection):
        """Updates the cursor position information."""
        toplevel = self.get_toplevel()
        toplevel.update_position_info()

    def mark_cb(self, textbuffer, iter, mark):
        """Updates the cursor position information."""
        if mark.get_name() == 'insert':
            toplevel = self.get_toplevel()
            toplevel.update_position_info()

    def changed_cb(self, textbuffer):
        """Updates the cursor position information."""
        toplevel = self.get_toplevel()
        toplevel.update_position_info()

    def goto_mark(self, mark):
        """Moves the cursor to the mark position and update the view so that the cursor is on screen."""
        buffer = self.get_buffer()
        start = buffer.create_mark(None, buffer.get_iter_at_mark(buffer.get_insert()), False)
        end = buffer.create_mark(None, buffer.get_iter_at_mark(buffer.get_selection_bound()), False)
        self.meta.bookmark((start, end))
        iter = buffer.get_iter_at_mark(mark)
        buffer.place_cursor(iter)
        view = self.get_view()
        view.scroll_to_mark(mark, 0.0, use_align=True, xalign=1.0, yalign=0.25)
        view.grab_focus()

    def forward_to_parentesis(self):
        """Moves the cursor to the next matching parentesis."""
        self.get_buffer().forward_to_parentesis()
        view = self.get_view()
        view.scroll_to_mark(self.get_buffer().get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)
        view.grab_focus()        

    def back_to_parentesis(self):
        """Moves the cursor to the previous matching parentesis."""
        self.get_buffer().back_to_parentesis()
        view = self.get_view()
        view.scroll_to_mark(self.get_buffer().get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)
        view.grab_focus()        

class File_Properties_Dialog(gtk.Window):
    """Dialog to display infomation about a file."""

    def __init__(self, info = None):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        if info == None:
            self.set_title("New File")
            self.show_all()
            return
        self.set_title("Details of %s" %s (info.name))
        self.set_default_size(300, 200)
        self.show_all()

def new_window(source, page, x, y, data = None):
    """Create a new window for a notebook widget containing the 'torn off' tab."""
    window = contexts['null'].new_window(widget = page)
    return window.tab_widget

class Request_Line_Number(gtk.Dialog):
    """Line number requester dialog."""
    def __init__(self, title = None, parent = None, flags = 0, buttons = None):
        gtk.Dialog.__init__(self, title, parent, flags, buttons)
        self.entry = gtk.Entry()
        self.get_child().pack_start(self.entry)
        self.entry.show_all()
        self.entry.connect('key-release-event', self.key_release)

    def key_release(self, widget, event):
        """Editing complete close widget."""
        if event.keyval == gtk.keysyms.Return:
            self.response(gtk.RESPONSE_OK)

    def get_line_number(self):
        """Returns the content of the entry box."""
        return self.entry.get_text()

class Tab_Label(gtk.HBox):
    """Manages the widget used in the tabs of the notebook."""

    def __init__(self, text, page_widget):
        """Initialise the elements of the tab widget."""
        gtk.HBox.__init__(self)
        label = gtk.Label(text)
        self.pack_start(label)
        close_image = gtk.Image()
        close_image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
        box = gtk.EventBox()
        box.connect('button-release-event', self.close_cb, page_widget)
        box.set_events(gtk.gdk.BUTTON_RELEASE_MASK)
        box.add(close_image)
        self.pack_start(box, False, False)
        self.show_all()
        label.connect('button-press-event', self.button_cb, page_widget)

    def close_cb(self, widget, event, page_widget):
        """Closes this page."""
        page_widget.close()

    def button_cb(self, widget, event, page_widget):
        """Reload the page."""
        if event.type == gtk.gdk._2BUTTON_PRESS:
            buffer = page_widget.get_buffer()
            if buffer.meta.command:
                buffer.run_command()
            else:
                pass
                #buffer.load_file(buffer.meta.full_path, buffer.meta.version)

class Event:
    """Base class of events used to create macros."""

    def __call__(self):
        """Override this method to perform event actions."""
        pass

class Cursor_Movement(Event):
    """Manages aggregation of cursor movement events."""
    
    def __init__(self, step, count, buffer):
        """Force the movement to be of some value."""
        self.buffer = buffer
        self.step = step
        self.count = count
    
    def __iadd__(self, other):
        if self.buffer != other.buffer or self.step != other.step:
            raise NotImplemented
        self.count += other.count
        if self.count == 0:
            return None
        else:
            return self

    def __call__(self):
        """Replay cursor movement."""
        #log.log.append(log.Event('Debug', 'cursor movement called', self.step))
        functions = {gtk.MOVEMENT_LOGICAL_POSITIONS : gtk.TextIter.forward_chars,
                     gtk.MOVEMENT_VISUAL_POSITIONS : gtk.TextIter.forward_chars,
                     gtk.MOVEMENT_WORDS : gtk.TextIter.forward_visible_word_end,
                     gtk.MOVEMENT_DISPLAY_LINES : gtk.TextIter.forward_visible_lines,
                     gtk.MOVEMENT_DISPLAY_LINE_ENDS : gtk.TextIter.forward_chars,
                     gtk.MOVEMENT_PARAGRAPHS : gtk.TextIter.forward_sentence_ends,
                     gtk.MOVEMENT_PARAGRAPH_ENDS : gtk.TextIter.forward_sentence_ends,
                     gtk.MOVEMENT_PAGES : gtk.TextIter.forward_chars,
                     gtk.MOVEMENT_BUFFER_ENDS : gtk.TextIter.forward_chars}
 
        cursor = self.buffer.get_iter_at_mark(self.buffer.get_insert())
        functions[self.step](cursor, self.count)
        self.buffer.place_cursor(cursor)
        
    def __eq__(self, other):
        """Compare cursor movements.
        To allow for the cursor movement objects to be concatenated objects are considered a
        single movement if the other item contains a movement in the
        same direction."""

        if not other:
            return False

        if self.step != other.step:
            return False

        # Check for a single movement of the step size
        if abs(self.count) == 1 or abs(other.count) == 1:
            if self.left_right * other.left_right > 0:
                return True
            
        return self.count == other.count

class Insert(Event):
    """Manages aggregation of input events."""
    string = ""
    
    def __init__(self, string, buffer):
        """Store the initial string."""
        self.buffer = buffer
        string += str(string)
    
    def __iadd__(self, other):
        if self.buffer != other.buffer:
            raise NotImplemented
        return self + other.string

    def __call__(self):
        """Replay insert."""
        log.log.append(log.Event('Debug', 'insert called'))
        self.buffer.insert_at_cursor(self.string)
        
    def __eq__(self, other):
        """Compare insert events.
        To allow for the string contatenation inserts are considered equal if one is at the
        start of the other."""

        if not other:
            return False

        compare = cmp(self.string, other.string)
        # Check for full match
        if compare == 0:
            return True

        # Check for one string starting the other 
        if compare > 0:
            return self.string.startswith(other.string)
        else:
            return other.string.startswith(self.string)

class Macro:
    """Manage macros."""
    def __init__(self, name = None):
        self.name = name
        self.sequence = []
    
    def add_event(self, event):
        """Adds an event to this macro, combining with previous event where possible."""
        try:
            self.sequence[-1] += event
            if self.sequence[-1] == None:
                self.sequence.pop()
        except (TypeError, IndexError):
            self.sequence.append(event)
            
    def playback(self):
        """Replays the entire macro."""
        [event() for event in self.sequence]

    def replay(self):
        """Replays the last sequence found in the macro."""
        last_event = self.sequence[-1]
        [event() for event in self.sequence]

class Exit_Confirm_Dialog(gtk.Dialog):

    def __init__ (self, buffers):
        """Create a dialog listing the open files."""
        gtk.Dialog.__init__(self, title = 'Really Quit?',
                            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                       gtk.STOCK_QUIT, gtk.RESPONSE_OK))
        self.widgets = []
        self.buffers = buffers
        self.create_widgets()
        self.show_all()

    def save_buffer_cb(self, button, buffer):
        """Save the referenced buffer."""
        buffer.target.save_file()
        if not buffer.target.get_modified():
            self.buffers.remove(buffer)
        self.create_widgets()

    def save_all_cb(self, button):
        """Save the referenced buffer."""
        for buffer in [self.buffers]:
            buffer.target.save_file()
            if not buffer.target.get_modified():
                self.buffers.remove(buffer)
        self.create_widgets()

    def create_widgets(self):
        """Create the widgets showing the unsaved buffers."""
        for child in self.widgets:
            self.vbox.remove(child)
        self.widgets = []
        
        if self.buffers:
            label = gtk.Label("Unsaved changes in the following buffers will be lost")
            self.vbox.pack_start(label, False, True)
            self.widgets.append(label)
            for buffer in self.buffers:
                box = gtk.HBox()
                label = gtk.Label(buffer.target.get_title())
                button = gtk.Button('Save')
                button.connect('clicked', self.save_buffer_cb, buffer)
                box.pack_start(label, False, False)
                box.pack_end(button, False, False)
                self.vbox.pack_start(box, False, True)
                self.widgets.append(box)
            if len(self.buffers) > 1:
                box = gtk.HBox()
                button = gtk.Button('Save All')
                button.connect('clicked', self.save_all_cb)
                box.pack_end(button, False, False)
                self.vbox.pack_start(box, False, True)
                self.widgets.append(box)
        else:
            label = gtk.Label("No unsaved changes detected.")
            self.vbox.pack_start(label, False, True)
            self.widgets.append(label)

        self.vbox.show_all()

class Application_Window(gtk.Window, dragndrop.Drop_Target_Interface):
    """Main window of the code editor application."""
    reopen = []
    control_keys = {}
    shift_keys = {}
    shift_control_keys = {}
    meta_keys = {}
    plain_keys = {}
    control_socket = None

    def __init__(self, x = 0, y = 0,  width = 800, height = 800):
        # text, tooltip_text, icon_file, callback, user_data
        tool_bar = [('Open', 'Load file', 'document-open.png', self.open_cb, None),
                    ('Save', 'Save file', 'document-save.png', self.save_file, None),
                    ('Save All', 'Save all files', 'document-save-all.png', self.save_all, None),
                    ('Back', 'Back', 'go-previous.png', self.back, 'back'),
                    ('Forward', 'Forward', 'go-next.png', self.forward, 'forward'),
                    ('Undo', 'Undo edit', 'edit-undo.png', self.undo, None),
                    ('Redo', 'Redo edit', 'edit-redo.png', self.redo, None)]

        key_map = [('C-s', self.save_file),
                   ('C-o', self.open_cb),
                   ('C-f', self.find_first),
                   ('C-g', self.choose_line),
                   ('C-l', self.set_show_line_numbers),
                   ('C-b', self.bookmark),
                   ('C-p', self.parse_buffer),
                   ('C-r', self.reload_cb),
                   ('C-h', self.hide_cb),
                   ('C-H', self.show_all_cb),
                   ('C-i', self.indent_cb),
                   ('C-I', self.outdent_cb),
                   ('M-Left', self.back_to_parentesis_cb),
                   ('M-Right', self.forward_to_parentesis_cb),
                   ('M-c', self.capitalise_cb),
                   ('M-l', self.lower_cb),
                   ('M-u', self.upper_cb),
                   ('M-v', self.swap_cb),
                   ('M-f', self.fold_cb),
                   ('M-F', self.unfold_cb),
                   ('M-g', self.context_fold_cb),
                   ('M-q', self.toggle_read_only_cb),
                   ('M-x', self.external_action),
                   ('F3', self.find_next),
                   ('S-F3', self.find_previous),
                   ('F9', self.insert_completion),
                   ('F12', self.visit),
                   ('M-period', self.repeat_last)]
        
        self.commands = {'open' : self.load_link}

        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self._fullscreen = False
        icons = []
        for image_file in glob.glob(config.image_root + '*x*/apps/accessories-text-editor.png'):
            icons.append(gtk.gdk.pixbuf_new_from_file(image_file))
        self.set_icon_list(*icons)

        gtk.notebook_set_window_creation_hook(new_window, None)

        self.connect('delete_event', self.delete_event)
        #self.connect('destroy', self.destroy)

        self.context = None

        self.set_title("Editor")
        if width > gtk.gdk.screen_width():
            width = gtk.gdk.screen_width()
        if height > gtk.gdk.screen_height():
            height = gtk.gdk.screen_height()

        # Negative width used to indicate a full screem window
        if width < 0:
            self.maximize()
        else:
            self.move(int(x), int(y))
            self.set_default_size(width, height)
        self.window_packing = gtk.VBox()

        sections = {}
        languages = language_manager.get_language_ids()
        for id in languages:
            language = language_manager.get_language(id)
            section = ">>>%s" % language.get_section()
            if section in sections:
                sections[section].append((language.get_name(), self.select_language, id))
            else:
                sections[section] = [(language.get_name(), self.select_language, id)]
        language_choices = [(key, sections[key]) for key in sorted(sections.keys())]
        parsers_choices = [('ooo' + key, self.set_parser, value) for key, value in parsers.items()]
        context_choices = [(context.name, self.load_context, context) for context in contexts.values()]
        self.recent_items = gtk.Menu()
        menu_definition = [('>>>File', [('New', self.new_cb, None),
                                ('Open', self.open_cb),
                                ('Reload', self.reload_cb),
                                ('Save', self.save_file, 'Save'),
                                ('Save As', self.save_file_as),
                                ('---', '---'),
                                ('Import', self.import_cb),
                                ('==>Recent Files', self.recent_items),
                                ('---', '---'),
                                ('Abandon', self.abandon),
                                ('Quit', self.destroy)]),
                    ('>>>Edit', [('copy', self.edit_cb, 'copy'),
                                ('cut', self.edit_cb, 'cut'),
                                ('paste', self.edit_cb, 'paste'),
                                ('swap', self.edit_cb, 'swap'),
                                ('---', '---'),
                                ('Undo', self.undo, 'undo'),
                                ('Redo', self.redo, 'redo'),
                                ('---', '---'),
                                ('Indent', self.indent_cb),
                                ('Outdent', self.outdent_cb)]),
                    ('>>>View', [('Bookmark', self.bookmark, 'redo'),
                                ('Back', self.back, 'back'),
                                ('Forward', self.forward, 'forward'),
                                ('New view', self.new_view_cb),
                                ('---', '---'),
                                ('Show All', self.show_all_cb),
                                ('View Item', self.view_item_cb),
                                ('---', '---'),
                                ('Change Font', self.select_font),
                                ('>>>Set language', language_choices),
                                ('>>>Set parser', parsers_choices),
                                ('oxoHighlight Syntax', self.syntax_highlight_cb),
                                ('oxoShow _line numbers', self.set_show_line_numbers)]),
                    ('>>>Tools', [('>>>Context Manager', [('Save Context', self.save_context),
                                                          ('>>>Import Context', context_choices),
                                                          ('>>>Load Context', context_choices)]),
                                  ('Show Version Information', self.show_versions_cb),
                                  ('Show Event Log', self.show_log_cb),
                                  ('Lookup Acronym', self.acronyms),
                                  ('Run External Program', self.external_action),
                                  ('---', '---'),
                                  ('Save defaults', self.save_defaults_cb)]),
                    ('>>>Windows', [('New Window', self.new_window_cb)]),
                    ('>>>Help', [('About', self.about_cb),
                                 ('File Info', self.file_info)])]
        self.menu_bar = self.build_menu(menu_definition, gtk.MenuBar())
        self.window_packing.pack_start(self.menu_bar, False)

        self.tool_bar = self.build_toolbar(tool_bar)
        self.window_packing.pack_start(self.tool_bar, False, False)
        
        self.search_box = gtk.expander_new_with_mnemonic('_Search')
        self.search_box.add(Search_Functions())
        self.window_packing.pack_start(self.search_box, False, False)

        self.add_status_bar()

        self.tab_widget = gtk.Notebook()
        self.tab_widget.set_group_id(1)
        self.tab_widget.set_scrollable(True)
        self.tab_widget.connect_after('switch-page', self.page_changed)
        self.tab_widget.connect('page-removed', self.page_removed_cb)
        self.window_packing.pack_start(self.tab_widget)
        self.add(self.window_packing)

        self.window_packing.pack_end(self.status_bar, False, False)
        self.connect('window-state-event', self.window_state_cb)
        self.set_key_mappings(key_map)
        self.auto_macro = Macro()
        self.connect('key-release-event', self.key_release_cb)
        self.show_all()
        self.add_drop_format(dragndrop.TEXT, self.drop_text)
        self.add_drop_format(dragndrop.FILES, self.drop_files)
        self.drop_handle = Drop_Target(self)
        self.create_server(config.command_port)
        self.update_recent_items_menu()
        
    def new_window_cb(self, item):
        """Create a new window."""
        contexts['null'].new_window(Application_Window())

    def update_recent_items_menu(self):
        """Refresh the recent items menu."""
        recent = [(item.get_short_name().replace('file: ', ''),
                   self.open_cb, item.get_uri()) for item in manager.get_items()]
        children = self.recent_items.get_children()
        for child in children:
            self.recent_items.remove(child)
        self.recent_items = self.build_menu(recent, self.recent_items, use_underline = False)

    def add_status_bar(self):
        """Add a status bar to the window."""
        self.status_bar = gtk.Statusbar()
        frame = gtk.Frame()
        self.function_name = gtk.Label()
        self.function_name.set_tooltip_text('Current Function')
        frame.add(self.function_name)

        self.view_context = gtk.combo_box_new_text()
        self.view_context.set_tooltip_text('Context')
        self.view_context.append_text('None')
        for context in contexts.keys():
            self.view_context.append_text(context)
        self.view_context.append_text('New Context')
        self.view_context.connect('changed', self.view_context_menu_cb)

        self.file_status = gtk.Button()
        self.file_status.connect('clicked', self.toggle_read_only_cb)
        self.line_number = gtk.Entry()
        self.line_number.set_tooltip_text('Line number')
        self.line_number.set_width_chars(5)
        self.line_number.connect('key-release-event', self.goto_line_cb)
        self.column_number = gtk.Entry()
        self.column_number.set_tooltip_text('Column number')
        self.column_number.set_width_chars(3)
        self.column_number.connect('key-release-event', self.goto_position_cb)
        self.offset = gtk.Entry()
        self.offset.set_tooltip_text('Character offset')
        self.offset.set_width_chars(7)
        #self.offset.connect('key-release-event', self.goto_position_cb)
        self.status_bar.pack_end(self.offset, False, False)
        self.status_bar.pack_end(self.column_number, False, False)
        self.status_bar.pack_end(self.line_number, False, False)
        self.status_bar.pack_end(self.file_status, False, False)
        self.status_bar.pack_end(self.view_context, False, False)
        self.status_bar.pack_end(frame, False, False)

    def view_context_menu_cb(self, button):
        """Display a context menu pop up."""
        menu_definition = [(name, self.set_context_cb, contexts[name]) for name in contexts.keys()]
        menu = build_menu(menu_definition)
        menu.popup(None, None, None, 1, gtk.get_current_event_time())
    
    def set_context_cb(self, item, context):
        log.log.append(log.Event('Information', 'Context: %s ' % (context.name)))

    def create_server(self, port):
        """Opens a listening socket to recieve control commands."""
        self.control_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        try:
            self.control_socket.bind(('0.0.0.0', port))
            gobject.io_add_watch(self.control_socket, gobject.IO_IN, self.receive_message_cb)
            log.log.append(log.Event('Information', 'Listening on port: %s' % (str(port))))

        except socket.error:
            log.log.append(log.Event('Warning',
                                      "Couldn't bind to the socket %s as editor server." % (str(port))))

    def receive_message_cb(self, source, condition):
        """Process IO from the command socket."""
        # Catch all exceptions to insure that the function returns true
        # so that it remains installed as the IO handler for the socket.
        #
        action = None
        data = None
        try:
            if condition == gobject.IO_IN:
                message = source.recv(4096)
                log.log.append(log.Event('Information', 'Received message: %s' % (message)))
                document = xml.dom.minidom.parseString(message)
                for node in document.documentElement.childNodes:
                    if node.tagName == 'action':
                        action = node.firstChild.nodeValue
                    if node.tagName == 'data':
                        data = node.firstChild.nodeValue
                        
                if action in self.commands:
                    self.commands[action](data)
                else:
                    if action == 'edit':
                        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
                        target = active_page.get_buffer()
                    
                    if data:
                        data = data.split(',')
                        command = data[0]
                        if hasattr(target, command):
                            if len(data) > 1:
                                self.update_status('Received: %s(%s) command' % (command, ','.join(data[1:])))
                                target.__getattribute__(command)(*data[1:])
                            else:
                                self.update_status('Received: %s() command' % (command))
                                target.__getattribute__(command)()
                        else:
                            self.update_status("Invalid %s command: %s" % (action, command))
            else:
                self.update_status("Unexpected IO condition: %s" % (str(condition)))
        except:
            self.update_status("Exception: %s in receive_message_cb" % (str(sys.exc_info()[0])))
        finally:
            return True

    def window_state_cb(self, widget, event):
        """Respond to a window state callback event."""
        self._fullscreen = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED)

    def about_cb(self, widget, data = None):
        """Displays the application 'about' window."""
        About_Window()

    def edit_cb(self, widget, data = None):
        """Performs requested 'edit' action"""
        if not data:
            return
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        clipboard = gtk.Clipboard()
        if data == 'cut':
            buffer.cut_clipboard(clipboard, active_page.get_view().get_editable())
        elif data == 'copy':
            buffer.copy_clipboard(clipboard)
        elif data == 'paste':
            buffer.paste_clipboard(clipboard, None, active_page.get_view().get_editable())
        elif data == 'swap':
            bounds = buffer.get_selection_bounds()
            text = buffer.get_text(bounds[0], bounds[1])
            buffer.paste_clipboard(clipboard, None, active_page.get_view().get_editable())
            clipboard.set_text(text)

    def external_action(self, widget, data = None):
        """Run an external progam and display the results in a new tab."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if active_page.get_buffer().meta.full_path:
            path = active_page.get_buffer().meta.full_path
        else:
            path = None
        dialog = External_Command()
        dialog.call_back = self.command_callback

    def command_callback(self, cmd, result):
        """Display a tab containing the results of external command execution."""
        if cmd:
            active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
            new_tab = self.new_tab(active_page.get_buffer().meta.path, command = cmd)
            buffer = new_tab.get_buffer()
            buffer.run_command()

    def show_log_cb(self, widget, data = None):
        """Displays the event log window."""
        log.Log_Dialog.show(log.log)

    def show_versions_cb(self, widget, data = None):
        """Displays the version information window for the file in the current buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        Version_Window(buffer.meta.full_path, self.get_versions, self.open_version, self.compare)

    def open_version(self, path, version):
        """Load a specified version of a file."""
        active_page = self.new_tab(path, version)

    def compare(self, string_1, string_2, title = 'Diff'):
        """Load a specified version of a file."""
        active_page = self.new_tab(None)
        diff = difflib.Differ()
        result = list(diff.compare(string_1.splitlines(True), string_2.splitlines(True)))
        active_page.get_buffer().set_text(''.join(result))
        self.set_title("Editor: %s" % (title))

    def get_versions(self, path):
        """Returns the version information for the given file."""
        dir, filename = os.path.split(path)
        if filename[:2] == 's.':
            program = [cssc_root + 'prs', '-e', "-d':I:|:C:'", path.replace('\\', '/')]
            process = subprocess.Popen(program, stdout = subprocess.PIPE)
            output = process.stdout
            results = output.read()
            output.close()
            results = [line.split('|') for line in results.split('\n') if line]
        else:
            results = ['N/A', 'No version information available']
        return results

    def acronyms(self, widget, data = None):
        """Look up an acronym."""
        pass

    def set_show_line_numbers(self, widget):
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.meta.show_line_numbers = not active_page.meta.show_line_numbers
        view = active_page.get_view().set_show_line_numbers(active_page.meta.show_line_numbers)

    def syntax_highlight_cb(self, widget):
        """Toggles the highlight syntax setting of the active buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.get_buffer().toggle_highlight_syntax()

    def get_handle(self):
        """Returns the OS window handle for this widget (for use as a drop target)."""
        return self.window.handle

    def drop_files(self, data, point):
        """Loads the dropped file(s)."""
        for filename in dragndrop.get_files(data):
            log.log.append(log.Event('Information', 'Loading file %s' % (filename)))
            if not os.path.exists(filename):
                filename = urllib.unquote(filename)
                filename = urlparse.urlsplit(filename).path
            if os.path.isdir(filename):
                for sub_file in glob.glob("*"):
                    if os.path.isfile(sub_file):
                        self.load_file(sub_file)
            else:
                self.load_file(filename)

    def drop_text(self, data, point):
        """Adds the text to the window"""
        if os.path.exists(urlparse.urlsplit(data).path):
            self.load_file(data)
        else:
            location = gtk.gdk.window_at_pointer()
            position = self.text_window.get_iter_at_location(location[1], location[2])
            self.add_link(data, position)

    def current_location(self):
        """Returns the iter for the current text location."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        start = buffer.create_mark(None, buffer.get_iter_at_mark(buffer.get_insert()), False)
        end = buffer.create_mark(None, buffer.get_iter_at_mark(buffer.get_selection_bound()), False)
        return (start, end)

    def choose_line(self, widget, line = None):
        """Jump to specific line number."""
        dialog = Request_Line_Number(title = 'Enter line number',
                                     buttons = (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK))
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            line = dialog.get_line_number()
        dialog.destroy()
        if line:
            active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
            self.goto_line(active_page, line)
            active_page.get_view().grab_focus()

    def goto_line_cb(self, widget, event):
        """Jump to the specified line."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if gtk.gdk.keyval_name(event.keyval) == 'Return':
            active_page.get_view().grab_focus()
            return True
        else:
            line = widget.get_text()
            self.goto_line(active_page, line)

    def goto_position_cb(self, widget, event):
        """Jump to the specified line."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if gtk.gdk.keyval_name(event.keyval) == 'Return':
            active_page.get_view().grab_focus()
            return True
        else:
            column = widget.get_text()
            cursor = active_page.get_buffer().get_insert()
            iter = active_page.get_buffer().get_iter_at_mark(cursor)
            position = iter.get_line() + int(column)
            self.goto_position(active_page, position)

    def goto_position(self, page, position):
        """Jump to requested line."""
        self.bookmark(None)
        buffer = page.get_buffer()
        iter = buffer.get_iter_at_offset(int(position))
        buffer.place_cursor(iter)
        page.get_view().scroll_to_mark(buffer.get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)

    def forward_to_parentesis_cb(self, widget):
        """Move the cursor to the next matching parentesis."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.forward_to_parentesis()

    def back_to_parentesis_cb(self, widget):
        """Move the cursor to the previous matching parentesis."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.back_to_parentesis()

    def goto_line(self, page, line):
        """Jump to requested line."""
        self.bookmark(None)
        buffer = page.get_buffer()
        # Subtract 1 to get 0 based line count
        iter = buffer.get_iter_at_line(int(line) - 1)
        buffer.place_cursor(iter)
        page.get_view().scroll_to_mark(buffer.get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)

    def update_position_info(self):
        """Populates the position information in the status bar."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        cursor = active_page.get_buffer().get_insert()
        iter = active_page.get_buffer().get_iter_at_mark(cursor)
        line = iter.get_line() + 1
        column = iter.get_line_offset()
        tags = iter.get_tags()
        self.function_name.set_markup('')
        if tags:
            named_tags = [tag.get_property('name') for tag in tags if tag.get_property('name')]
            text = ''
            for tag in named_tags:
                if tag.startswith('scope:') and len(tag) > len(text):
                    text = tag
            text = text.replace('scope:', '')
            if text:
                #label = gtk.Label(text)
                self.function_name.set_markup(text)
        self.line_number.set_text(str(line))
        self.column_number.set_text(str(column))
        self.offset.set_text(str(iter.get_offset()))

    def bookmark(self, widget, data = None):
        """Set a bookmark in the to return to."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        info = active_page.meta
        info.bookmark(self.current_location())

    def goto(self, location):
        """Select the text specified by the location. Location is a tuple of start and end marks."""
        start, end = location
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        start_iter = buffer.get_iter_at_mark(start)
        end_iter = buffer.get_iter_at_mark(end)
        buffer.select_range(start_iter, end_iter)
        active_page.get_view().scroll_to_iter(start_iter, 0.0, use_align=True, xalign=1.0, yalign=0.25)

    def back(self, widget, data = None):
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        info = active_page.meta
        if info.can_go_back():
            self.goto(info.go_back(self.current_location()))

    def forward(self, widget, data = None):
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        info = active_page.meta
        if info.can_go_forward():
            self.goto(info.go_forward(self.current_location()))

    def undo(self, widget, data = None):
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        if buffer.can_undo():
            buffer.undo()

    def redo(self, widget, data = None):
        """Redo the presvious editing action."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        if buffer.can_redo():
            buffer.redo()

    def capitalise_cb(self, widget):
        """Capitalise the current word."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        current_buffer = active_page.get_buffer()
        current_buffer.capitalise()

    def lower_cb(self, widget):
        """Change text to lower case."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        current_buffer = active_page.get_buffer()
        current_buffer.lower()

    def upper_cb(self, widget):
        """Change text to upper case."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        current_buffer = active_page.get_buffer()
        current_buffer.upper()

    def swap_cb(self, widget):
        """Swap the contents the clipboard and current selection."""
        self.edit_cb(widget, 'swap')

    def update_title(self, page):
        """Updates the window title based on the state of the current buffer."""
        title = page.get_buffer().get_title()
        self.tab_widget.get_tab_label(page).get_children()[0].set_text(title)
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if page == active_page:
            self.set_title("Editor: %s" % (page.get_buffer().get_title(full=True)))

    def hide_cb(self, widget):
        """Hide the current selection."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.hide_selection()
        self.update_title(active_page)

    def show_all_cb(self, widget):
        """Show any hidden text."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        text_buffer = active_page.get_buffer()
        text_buffer.show_all()
        self.update_title(active_page)
        active_page.get_view().scroll_to_mark(text_buffer.get_insert(),
                                              0.0,
                                              use_align=True,
                                              xalign=1.0,
                                              yalign=0.25)

    def view_item_cb(self, widget):
        """Hide all but the current item."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        text_buffer = active_page.get_buffer()
        text_buffer.fold()
        self.update_title(active_page)

    def unfold_cb(self, widget):
        """Hide all but the current item."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        text_buffer = active_page.get_buffer()
        text_buffer.unfold()
        self.update_title(active_page)

    def fold_cb(self, widget):
        """Only show the current code block."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.fold()
        self.update_title(active_page)

    def max_fold_cb(self, widget):
        """Only show the current code block."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.fold()
        self.update_title(active_page)

    def context_fold_cb(self, widget):
        """Only show the current code block and non blank preceeding lines."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.fold(True)
        self.update_title(active_page)

    def indent_cb(self, widget):
        """Hide the current selection."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.indent()

    def outdent_cb(self, widget):
        """Hide the current selection."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.outdent()

    def parse_buffer(self, widget, line = None):
        """Parse the current buffer to find definitions."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        parsed_result = C_Parser(buffer)

    def file_info(self, widget):
        """Display information about the current file."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        info = File_Info_Box(active_page.get_buffer())

    def delete_event(self, widget, event, data=None):
        """Close the window confirming if there are unsaved buffers."""
        # Return value determines if the main window will be closed,
        # if True the main window will not be destroyed with a "delete_event".
        save_contexts(config.context_file)
        if self.exit():
           windows = 0
           for context in contexts.values():
               if self in context.windows:
                   context.windows.remove()
                   windows += len(context.windows)
           if windows == 0:
               gtk.main_quit()
        else:
           # Don't close window
           return True

    def update_status(self, status):
        """Update the contents of the status bar."""
        if self.context:
            self.status_bar.pop(self.context)
        if status:
            self.context = self.status_bar.get_context_id('update')
            self.status_bar.push(self.context, status)
        else:
            self.context = None
        gtk.main_iteration(False)

    def select_language(self, widget, language):
        """Chage the laguage for the current buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        language = language_manager.get_language(language)
        active_page.get_buffer().set_language(language)

    def set_parser(self, widget, parser):
        """Change the laguage for the current buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.meta.parser = parser

    def select_font(self, widget):
        """Import data from a file."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        dialog = gtk.FontSelectionDialog("Select Font")
        dialog.set_font_name(active_page.meta.font.to_string())
        font = None
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            font = dialog.get_font_name()
        dialog.destroy()
        if font:
            active_page.meta.font = font
            active_page.get_view().modify_font(active_page.meta.font)

    def load_link(self, link):
        """Loads a url parsing for specifiv parameters"""
        if os.path.exists(link):
            path = link
        else:
            if urlparse.urlsplit(link).scheme == 'http':
                pass
            else:
                path = urlparse.urlsplit(link).path
        root, ext = os.path.splitext(link)
        parameters = None
        fragment = None
        if '?' in path:
            parameter_start = path.index('?')
            parameters = path[parameter_start + 1:]
            path = path[:parameter_start]
        if '#' in ext:
            fragment_start = ext.index('#')
            fragment = ext[fragment_start + 1:]
        settings = {'version' : None, 'command' : None}
        if parameters:
            for parameter in parameters.split('&'):
                name, value = parameter.split('=')
                settings[name] = value

        page = None
        for page_index in range(self.tab_widget.get_n_pages()):
            active_page = self.tab_widget.get_nth_page(page_index)
            if active_page \
               and active_page.get_buffer().meta.full_path \
               and active_page.get_buffer().meta.full_path.replace('\\', '/') == path.replace('\\', '/') \
               and active_page.get_buffer().meta.version == settings['version']:
                page = active_page
                self.tab_widget.set_current_page(page_index)

        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if not active_page.get_buffer().meta.full_path and not active_page.get_buffer().get_modified():
            page = active_page
            if settings['command']:
                buffer = page.get_buffer()
                buffer.full_path = path
                buffer.command = settings['command']
                buffer.run_command()
            else:
                page.get_buffer().load_file(path, settings['version'])
        if not page:
            page = self.new_tab(path, settings['version'], settings['command'])

        if 'line' in settings:
            self.goto_line(page, settings['line'])
        elif fragment:
            view = page.get_view()
            buffer = page.get_buffer()
            result = buffer.get_start_iter().forward_search(fragment, gtk.TEXT_SEARCH_TEXT_ONLY)
            if result:
                (start, end) = result
                buffer.place_cursor(start)
                view.scroll_to_mark(buffer.get_insert(), 0.0, use_align=True, xalign=1.0, yalign=0.25)
                buffer.select_range(start, end)
        self.get_toplevel().present()

    def load_file(self, filename, version = None):
        """Load a file creating a new view if needed."""
        try:
            widget = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
            context = Context.get_context(widget)
            if widget.get_buffer().meta.full_path or widget.get_buffer().get_modified():
                new_buffer = context.new_buffer(filename, version)
                self.set_title("Editor: %s" % (filename))
                widget = context.add_page(self, new_buffer)
            else:
                widget.get_buffer().load_file(filename, version)
        except:
            log.log.append(log.Event('Warning',
                                     'Exception %s loading file %s' % (str(sys.exc_info()), filename)))
        return widget

    def new_cb(self, widget, name="New"):
        """Create a new empty tab."""
        self.new_tab(name)

    def new_tab(self, name="New", version=None, command=None):
        """Create a new tab."""
        context = [context for context in contexts.values() if context.find_window(target = self)]
        if len(context) > 0:
            context = context[0]
        else:
            context = contexts['null']

        if name:
            if command:
                buffer = context.new_buffer(name, command = command)
            else:
                if os.path.exists(name):
                    buffer = context.new_buffer(name, version)
                else:
                    buffer = context.new_buffer(None)
        else:
            buffer = context.new_buffer(None)

        new_page = context.add_page(self, buffer)

        self.set_title("Editor: %s" % (name))
        return new_page
            
    def new_view_cb(self, widget):
        current_view = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        return contexts['null'].add_page(self, current_view.get_buffer())

    def open_cb(self, widget, data=None):
        """Import data from a file."""
        if not data:
            dialog = gtk.FileChooserDialog("Open", None, gtk.FILE_CHOOSER_ACTION_OPEN,
                                           (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
            widget = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
            if widget.get_buffer().meta.path:
                dialog.set_current_folder(widget.get_buffer().meta.path)
            filename = None
            response = dialog.run()
            if response == gtk.RESPONSE_OK:
                filename = dialog.get_filename()
            dialog.destroy()
        else:
            filename = data
        if filename:
            filename = os.path.normpath(filename)
            self.update_status("Loading %s..." % (filename))
            self.load_link(filename)
            self.update_status("Loaded %s." % (filename))

    def import_cb(self, widget):
        """Import a file from a URL."""
        dialog = gtk.Dialog("Open URL",
                            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                       gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        entry = gtk.Entry()
        dialog.vbox.pack_start(entry, False, False)
        dialog.vbox.show_all()
        filename = None
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            filename = entry.get_text()
        dialog.destroy()
        if filename:
            self.update_status("Loading %s..." % (filename))
            self.load_link(filename)
            self.update_status("Loaded %s." % (filename))                                                       

    def reload_cb(self, widget):
        """Reload the current data."""
        self.reload()

    def reload(self):
        """Reload the current data."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        if buffer.meta.command:
            buffer.run_command()
        else:
            buffer.load_file(buffer.meta.full_path, buffer.meta.version)

    def exit(self):
        """Confirm close depending on configuration."""
        
        for context in contexts.values():
            window = context.find_window(target = self)
            if window:
                break
        
        buffers = set()
        if window:
            buffers = set([page.buffer for page in window.pages
                           if page.buffer.target and page.buffer.target.get_modified()])

        exit = True
        if config.always_confirm_close or buffers:
            request = Exit_Confirm_Dialog(buffers)
            response = request.run()
            request.destroy()

            exit = {gtk.RESPONSE_CANCEL : False,
                    gtk.RESPONSE_NONE   : False,
	                gtk.RESPONSE_DELETE_EVENT : False,	
                    gtk.RESPONSE_OK     : True} [response]

        return exit

    def abandon(self, wodget):
        """Exit the application without updating the context."""
        gtk.main_quit()

    def save_file_as(self, widget, data = None):
        """Choose destination file for saving to."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.get_buffer().save_file_as()

    def save_file(self, widget, data=None):
        """Save the current file."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        active_page.get_buffer().save_file()

    def save_all(self, widget, data=None):
        """Save all modified files."""
        for index in range(self.tab_widget.get_n_pages()):
            self.tab_widget.get_nth_page(index).get_buffer().save_file()

    def update_menu_state(self, menu, buffer):
        """Update the menu state based on the settings for the current buffer."""
        pass

    def build_menu(self, menu_definition, root, use_underline=True):
        """Create a popup menu from the definition."""
        menu = root

        group = None

        for definition in menu_definition:
            text = definition[0]
            function = definition[1]
            if len(definition) > 2:
                data = definition[2]
            else:
                data = None
            if text == '---':
                item = gtk.SeparatorMenuItem()
                group = None
            elif text.startswith('>>>'):
                group = None
                item = gtk.MenuItem(text[3:], use_underline)
                item.set_submenu(self.build_menu(function, root = gtk.Menu()))
            elif text.startswith('==>'):
                group = None
                item = gtk.MenuItem(text[3:])
                item.set_submenu(function)
            elif text.startswith('oo'):
                item = gtk.RadioMenuItem(group, text[3:])
                if text[:3] == 'x':
                    item.set_active(True)
                item.connect("activate", function, data)
                group = item
            elif text.startswith('ox'):
                item = gtk.CheckMenuItem(text[3:], )
                if text[3] == 'x':
                    item.set_active(True)
                if data:
                    item.connect("activate", function, data)
                else:
                    item.connect("activate", function)
                group = None
            else:
                group = None
                item = gtk.MenuItem(text, use_underline)
                if data:
                    item.connect("activate", function, data)
                else:
                    item.connect("activate", function)
            menu.append(item)
            item.show()
        return menu

    def build_toolbar(self, toolbar_definition):
        """Creates a toolbar from the supplied definition."""
        toolbar = gtk.Toolbar()
        tooltip = gtk.Tooltip()
        for text, tooltip_text, icon_file, callback, user_data in toolbar_definition:
            if text[:3] == '---':
                tool_bar_item = gtk.SeparatorToolItem()
            else:
                if icon_file:
                    try:
                        icon = gtk.Image()
                        pixbuf = gtk.gdk.pixbuf_new_from_file(defaults['image_root'] + "22x22/actions/" + icon_file)
                        icon.set_from_pixbuf(pixbuf)
                    except:
                        log.log.append(log.Event('Error', 'Exception: %s' % (sys.exc_info()[0])))
                else:
                    icon = None
                tool_bar_item = gtk.ToolButton(icon_widget = icon, label = text)
                if user_data:
                    tool_bar_item.connect('clicked', callback, user_data)
                else:
                    tool_bar_item.connect('clicked', callback)
                tool_bar_item.set_tooltip_text(tooltip_text)
            toolbar.insert(tool_bar_item, -1)
        return toolbar

    def func(self, texttag, data):
        log.log.append(log.Event('Debug', 'Tag: %s' % (str(texttag.get_property('background-set')))))

    def visit(self, widget):
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if active_page.meta.parser:
            active_page.meta.parser(active_page)
        elif active_page.get_buffer().meta.parser:
            active_page.get_buffer().meta.parser(active_page)

    def find_first(self, widget):
        """Moves to the first instance of the text in the search box."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        self.search_box.set_expanded(True)
        self.search_box.grab_focus()
        self.search_box.get_child().reg_exp_text.grab_focus()
        self.search_box.get_child().find_first_cb(active_page)

    def find_next(self, widget):
        """Moves to the next instance of the text in the search box."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if not self.search_box.get_child().find_next_cb(active_page):
            self.update_status("Pattern not found")

    def find_previous(self, widget):
        """Moves to the previous instance of the match in the search box."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if not self.search_box.get_child().find_previous(active_page):
            self.update_status("Pattern not found")

    def toggle_read_only_cb(self, widget):
        """Changes the write state of the current buffer.""" 
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.meta.read_only = not buffer.meta.read_only
        title = buffer.get_title(markup = True)
        for view in buffer.meta.views:
            view.set_title(title)
            view.parse()
        self.set_title("Editor: %s" % (buffer.get_title(full=True)))

    def insert_completion(self, widget = None):
        """Replaces the current word with alternate for the buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.insert_completion()        

    def add_completions(self, completions):
        """Add a set of completions to the current buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        buffer.add_completions(completions)        

    def repeat_last(self, widget):
        """Repeats the last event recorded in the macro buffer."""
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        buffer = active_page.get_buffer()
        self.auto_macro.sequence[-1]()

    def key_release_cb(self, widget, event):
        """If the key has an assigned function process the event else let it proporgate."""
        #log.log.append(log.Event('Debug', 'Key released %s' % (str(gtk.gdk.keyval_name(event.keyval)))))

        #if event.keyval in [gtk.keysyms.Up, gtk.keysyms.Down,
        #                    gtk.keysyms.Left, gtk.keysyms.Right]:
        #    active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        #    buffer = active_page.get_buffer()
        #    self.auto_macro.add_event(Cursor_Movement(event.keyval, buffer))
        if (event.state & gtk.gdk.CONTROL_MASK) \
            and (event.state & gtk.gdk.SHIFT_MASK) \
            and (event.keyval in self.shift_control_keys.keys()):

            self.shift_control_keys[event.keyval](widget)
            return False

        elif (event.state & gtk.gdk.CONTROL_MASK) \
            and (event.keyval in self.control_keys.keys()):
            self.control_keys[event.keyval](widget)
            return False

        elif (event.state & gtk.gdk.MOD1_MASK) \
            and (event.keyval in self.meta_keys.keys()):
            self.meta_keys[event.keyval](widget)
            return False            

        elif (event.state & gtk.gdk.SHIFT_MASK) \
            and (event.keyval in self.shift_keys.keys()):
            self.shift_keys[event.keyval](widget)
            return False            

        elif event.keyval in self.plain_keys.keys():
            self.plain_keys[event.keyval](widget)
            return True

    def set_key_mappings(self, key_map):
        """Creates the mappings used when evaluating key presses."""
        for item in key_map:
            key_description = item[0]
            function = item [1]
            if key_description[0] == 'C':
                if key_description[2:].isupper():
                    self.shift_control_keys[gtk.gdk.keyval_from_name(key_description[2:])] = function
                else:
                    self.control_keys[gtk.gdk.keyval_from_name(key_description[2:])] = function
            elif key_description[0] == 'M':
                self.meta_keys[gtk.gdk.keyval_from_name(key_description[2:])] = function
            elif key_description[0] == 'S':
                self.shift_keys[gtk.gdk.keyval_from_name(key_description[2:])] = function
            else:
                self.plain_keys[gtk.gdk.keyval_from_name(key_description)] = function

    def page_changed(self, widget, page, page_index, data = None):
        """Change the window title to relect the selected page."""
        active_page = self.tab_widget.get_nth_page(page_index)
        title = active_page.get_buffer().get_title(full=True)
        self.set_title("Editor: %s" % (title))
        self.update_position_info()
        self.file_status.set_label(active_page.get_buffer().meta.get_read_status())
        #self.view_context.get_children()[0].set_markup(active_page.meta.get_context())

    def page_removed_cb(self, notebook, child, page_num, data = None):
        """Check if there are pages left - if not close parent."""
        # Notify the context that the page has been removed from this window
        for context in contexts:
            contexts[context].remove_page(self, child)
        if notebook.get_n_pages() < 1:
            gtk.Widget.destroy(self)

    def modified(self, buffer, modified_page):
        """Updates the title based on the modification state."""
        title = modified_page.meta.name
        if modified_page.get_child().get_buffer().get_modified():
            title += '*'
        self.tab_widget.get_tab_label(modified_page).get_children()[0].set_text(title)
        active_page = self.tab_widget.get_nth_page(self.tab_widget.get_current_page())
        if modified_page == active_page:
            self.set_title("Editor: %s" % (title))

    def save_context(self, widget):
        """Creates a context save dialog."""
        Context_Save_Request()

    def load_context(self, widget, context):
        context.activate()

    def save_defaults_cb(self, widget):
        """Save the current settings to the configuration file."""
        config.save_configuration()

class Configuration:
    """Manage the application configuration."""
    settings = {}

    def __init__(self, options):
        if options.config_file:
           config_file = options.config_file
        else:
           config_file = defaults['config_file']
        # Create a copy of the default items 
        for key, value in defaults.items():
            self.settings[key] = value

        # Create a copy of the default items 
        self.load_configuration()

        if options.session_file:
           self.settings['context_file'] = options.session_file

    def load_configuration(self):
        """Updates the settings based on the contents of the supplied xml file."""
        pattern = re.compile("\s*(<|>)\s*")
        config_file = os.path.expanduser(self.config_file)
        if os.path.exists(config_file):
            file = open(config_file)
            xml_string = file.read()
            file.close()
            # Remove pretty printing
            xml_string = pattern.sub(r'\1', xml_string)
            document = xml.dom.minidom.parseString(xml_string)
            for node in document.firstChild.childNodes:
                self.settings[node.tagName] = node.firstChild.nodeValue                    

    def save_configuration(self):
        """Update the configuration file based on the current settings."""
        xml_document = xml.dom.minidom.getDOMImplementation().createDocument(None, None, None)

        parent_node = xml_document.createElement('configuration')
        for key, value in self.settings.items():
            node = xml_document.createElement(key)
            node.appendChild(xml_document.createTextNode(str(value)))
            parent_node.appendChild(node)
        xml_document.appendChild(parent_node)

        config_file = os.path.expanduser(self.config_file)
        if not os.path.exists(os.path.dirname(config_file)):
            os.makedirs(os.path.dirname(config_file))
        file = open(config_file, 'w')
        file.write(xml_document.toprettyxml())
        file.close()

    def __getattr__(self, attribute):
        """Returns the value of the given attribute or None if the attibute hasn't been set."""
        result = None
        if attribute in self.settings:
            result = self.settings[attribute]
        return result

class Context_Save_Request(gtk.MessageDialog):
    """Manage the save context dialog."""

    def __init__(self):
        gtk.MessageDialog.__init__(self, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK_CANCEL)
        self.set_title("Save Context")
        self.set_markup('Enter new context name')
        self.entry = gtk.Entry()
        self.vbox.pack_start(self.entry, False, False)
        self.connect('response', self.response_cb)
        self.show_all()

    def response_cb(self, dialog, response_id):
        """Processes a response signal."""
        name = self.entry.get_text()
        self.destroy()
        if response_id == gtk.RESPONSE_OK:
            contexts[name] = copy.copy(contexts['null'])
            contexts[name].name = name
            save_contexts(config.context_file)

class About_Window(gtk.MessageDialog):
    """Display the 'about' window of the code editor application."""

    def __init__(self):
        gtk.MessageDialog.__init__(self, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK)
        self.set_title("About code_editor")
        self.set_markup('<b>Code Editor</b>\nCreated by Jon Dowdall.\nBased on gtkSourceView.')
        self.connect('response', self.response_cb)
        self.show()

    def response_cb(self, dialog, response_id):
        """Processes a response signal, the only response should be a click of the ok button."""
        # Only expected response is a close.
        self.destroy()

jump_stack = []
view_stack = []
def idle_cb():
    """Jump to the cursor when the window is mapped."""
    while jump_stack:
        view, position = jump_stack.pop()
        view.scroll_to_mark(position, 0.0, True, 1.0, 0.25)
    while view_stack:
        view, position = view_stack.pop()
        view.scroll_to_iter(position, 0.0, True, 1.0, 0.25)
    return False

class Context(object):
    """Manage context information."""
    frozen = False
    is_active = False

    @classmethod
    def get_context(cls, widget):
        """Returns the context associated with the given widget."""
        matched = [context for context in contexts.values()
                   if context.find_view(target = widget)]
        if len(matched) > 0:
            result = matched[0]
        else:
            window = widget.get_toplevel()
            matched = [context for context in contexts.values()
                       if context.find_window(target = window)]
            if len(matched) > 0:
                result = matched[0]
            else:
                contexts['null'].new_window(target = window)
                result = contexts['null']
        return result
            
    class Window:
        x, y = 0, 0
        width, height = 800, 800
        target = None
        page_number = 0
        __count = 0

        def __init__(self, target=None):
            """Creat the window, initialising the list of pages."""
            self.id = self.__count
            Context.Window.__count += 1
            self.pages = []
            if target:
                self.target = target
       
        def show(self, context):
            """Display the GUI window associated with this object."""
            self.target = Application_Window(self.x, self.y, self.width, self.height)
 
            for view in self.pages[:]:
                if view:
                    if view.buffer:
                        view.show(self)
                    else:
                        self.views.remove(page)
                else:
                    self.pages.remove(page)

            self.target.tab_widget.set_current_page(self.page_number)

        def remove_page(self, page):
            """Remove the specified page."""
            if page in self.pages:
                self.pages.remove(page)
            else:
               log.log.append(log.Event('warning', "Page %s not found in %s" % (str(page), str(self.pages))))
        
        def parse(self, node, context):
            """Loads the window parameters from the supplied node."""
            for child in node.childNodes:
                if child.tagName == 'id':
                    self.id = int(child.firstChild.nodeValue)
                elif child.tagName == 'position':
                    position = child.firstChild.nodeValue.split(',')
                    self.x, self.y = int(position[0]), int(position[1])
                elif child.tagName == 'size':
                    size = child.firstChild.nodeValue.split('x')
                    self.width, self.height = int(size[0]), int(size[1])
                elif child.tagName == 'page':
                    self.page_number = int(child.firstChild.nodeValue)
                elif child.tagName == 'views':
                    views = [int(view) for view in child.firstChild.nodeValue.split(',')]
                    self.pages.extend(views)

            if self.id >= Context.Window.__count:
                Context.Window.__count = self.id + 1

        def to_dom_node(self, document):
            """Generate dom node for this window."""
            parent_node = document.createElement('window')

            id_node = document.createElement('id')
            id_node.appendChild(document.createTextNode(str(self.id)))
            parent_node.appendChild(id_node)

            if self.target:
                self.x, self.y = self.target.get_position()
                if self.target._fullscreen:
                    self.width, self.height = -1, -1
                else:
                    self.width, self.height = self.target.get_size()
            
            position = "%d, %d" % (self.x, self.y)
            position_node = document.createElement('position')
            position_node.appendChild(document.createTextNode(position))
            parent_node.appendChild(position_node)
            
            size = "%d x %d" % (self.width, self.height)
            size_node = document.createElement('size')
            size_node.appendChild(document.createTextNode(size))
            parent_node.appendChild(size_node)

            page_node = document.createElement('page')
            page_number = str(self.target.tab_widget.get_current_page())
            page_node.appendChild(document.createTextNode(page_number))
            parent_node.appendChild(page_node)

            if self.pages:
                view_node = document.createElement('views')
                views = ', '.join([str(view.id) for view in self.pages])
                view_node.appendChild(document.createTextNode(views))
                parent_node.appendChild(view_node)

            return parent_node
        
    class Buffer:
        path = None
        version = None
        target = None
        cursor = 0
        command = None
        language = None
        view = None
        __count = 0
  
        def __init__(self, path=None):
            self.id = Context.Buffer.__count
            Context.Buffer.__count += 1
            self.path = path
        
        def load(self, version=None, is_unix_file=False, command=None):
            """Creates the buffer associated with this object."""
            if command:
                self.command = command
            if self.path and self.path != 'None':
                self.path = self.path.replace('\\', '/')
                self.target = Source_Buffer(self.path, version, is_unix_file, self.command)
            else:
                self.target = Source_Buffer(None, None, False, self.command)
            cursor = self.target.get_iter_at_offset(self.cursor)
            self.target.place_cursor(cursor)
    
        def parse(self, node):
            """Loads the buffer parameters for the supplied node."""
            for child in node.childNodes:
                if child.tagName == 'id':
                    self.id = int(child.firstChild.nodeValue)
                elif child.tagName == 'file':
                    self.path = child.firstChild.nodeValue
                elif child.tagName == 'cursor':
                    self.cursor = int(child.firstChild.nodeValue)
                elif child.tagName == 'language':
                    self.language = child.firstChild.nodeValue
                    if self.language == 'None':
                        self.language = None
                elif child.tagName == 'command':
                    self.command = child.firstChild.nodeValue
                    if self.command == 'None':
                        self.command = None

            if self.id >= Context.Buffer.__count:
                Context.Buffer.__count = self.id + 1
        
        def to_dom_node(self, document):
            """Generate dom node for this buffer."""
            parent_node = document.createElement('buffer')
            
            id_node = document.createElement('id')
            id_node.appendChild(document.createTextNode(str(self.id)))
            parent_node.appendChild(id_node)

            if self.target:
                self.path = str(self.target.meta.full_path).replace('\\', '/')
                self.command = str(self.target.meta.command).replace('\\', '/')
                self.cursor = self.target.get_iter_at_mark(self.target.get_insert()).get_offset()
                if self.language:
                    self.language = self.target.get_language().get_id()

            path_node = document.createElement('file')
            path_node.appendChild(document.createTextNode(self.path))
            parent_node.appendChild(path_node)
            
            if self.command:
                command_node = document.createElement('command')
                command_node.appendChild(document.createTextNode(self.command))
                parent_node.appendChild(command_node)
            
            cursor_node = document.createElement('cursor')
            cursor_node.appendChild(document.createTextNode(str(self.cursor)))
            parent_node.appendChild(cursor_node)
 
            if self.language:
                language_node = document.createElement('language')
                language_node.appendChild(document.createTextNode(self.language))
                parent_node.appendChild(language_node)

            return parent_node

    class View:
        offset = 0
        pane_width = 0
        target = None
        buffer_id = 0
        buffer = None
        __count = 0

        def __init__(self):
            """Initialise the view id using the next available number."""
            self.id = Context.View.__count
            Context.View.__count += 1

        def show(self, window):
            """Creates the widgets for the view."""
            self.target = Tree_Source_View(self.buffer.target)
            self.target.parse()
            self.target.set_position(int(self.pane_width))
            window.target.set_title("Editor: %s" % ("New"))
            label = Tab_Label(self.buffer.target.get_title(), self.target)
            page = window.target.tab_widget.append_page(self.target, label)
            window.target.tab_widget.set_current_page(page)
            window.target.tab_widget.set_tab_detachable(self.target, True)
            if self.offset:
                text_iter = self.buffer.target.get_iter_at_offset(int(self.offset))
                self.target.get_view().scroll_to_iter(text_iter, 0.0, use_align=True, xalign=1.0, yalign=0.25)
                view_stack.append((self.target.get_view(), text_iter))
            else:
                jump_stack.append((self.target.get_view(), self.buffer.target.get_insert()))
            #gobject.idle_add(idle_cb)

        def parse(self, node):
            """Loads the view parameters from the supplied node."""
            for child in node.childNodes:
                if child.tagName == 'id':
                    self.id = int(child.firstChild.nodeValue)
                if child.tagName == 'buffer':
                    self.buffer_id = int(child.firstChild.nodeValue)
                elif child.tagName == 'offset':
                    self.offset = int(child.firstChild.nodeValue)
                elif child.tagName == 'pane_width':
                    self.pane_width = int(child.firstChild.nodeValue)
        
        def to_dom_node(self, document):
            """Generate dom node for this view."""
            parent_node = document.createElement('view')

            id_node = document.createElement('id')
            id_node.appendChild(document.createTextNode(str(self.id)))
            parent_node.appendChild(id_node)

            if self.target:
                view = self.target.get_view()
                if view:
                    position = view.get_visible_rect()
                    text_iter = view.get_iter_at_location(position.x, position.y)
                    self.offset = str(text_iter.get_offset())
                    self.pane_width = str(self.target.get_position())
                else:
                    log.log.append(log.Event('Warning',
                                              "Target (%s) of view %s has no 'view'" % (str(self.target), str(self.id))))

            position_node = document.createElement('offset')
            position_node.appendChild(document.createTextNode(str(self.offset)))
            parent_node.appendChild(position_node)
            
            pane_node = document.createElement('pane_width')
            pane_node.appendChild(document.createTextNode(str(self.pane_width)))
            parent_node.appendChild(pane_node)

            buffer_node = document.createElement('buffer')
            buffer_node.appendChild(document.createTextNode(str(self.buffer_id)))
            parent_node.appendChild(buffer_node)

            return parent_node

    def __init__(self, name = None):
        """Set defaults for a context."""
        self.name = name
        self.description = None
        self.items = []
        self.windows = set()
        self.buffers = set()
        self.views = set()

    def find_window(self, id=None, target=None, create=False):
        """Find the window from the given id, create new window if not found."""
        result = None
        if id:
            result = find(lambda window: window.id == id, self.windows)
        elif target:
            result = find(lambda window: window.target == target, self.windows)
        if create and not result:
            result = self.new_window()
        return result

    def find_buffer(self, id=None, target=None, create=False):
        """Find the buffer from the given id, create new buffer if not found."""
        result = None
        if id:
            result = find(lambda buffer: buffer.id == id, self.buffers)
        elif target:
            result = find(lambda buffer: buffer.target == target, self.buffers)
        if create and not result:
            result = self.new_buffer()
        return result

    def find_view(self, id=None, target=None, create=False):
        """Find the view from the given id, create new view if not found."""
        result = None
        if id:
            result = find(lambda view: view.id == id, self.views)
        elif target:
            result = find(lambda view: view.target == target, self.views)
        return result

    def get_name():
        """Returns the context name."""
        return str(self.name)

    def activate(self, args = []):
        """Sets state as stored in this context."""
        self.is_active = True

        # Generate a list of active buffers.
        # i.e. those buffers for which a view is present.
        active_buffers = set()
        for window in self.windows:
            active_buffers |= set([page.buffer for page in window.pages])
        
        # Load all the active buffers
        for buffer in active_buffers:
            buffer.load()

        for window in self.windows:
            window.show(self)

        if not self.windows:
            self.new_window()

        if args:
            for file in args:
                if not os.path.dirname(file):
                    file = "%s/%s" % (root_path, file)
                buffer = self.new_buffer(file)
                self.add_page(window, buffer)

    def new_window(self, target=None, files=None, widget=None):
        """Adds a window object with the specified target."""
        new_window = self.Window(target)
        self.windows.add(new_window)

        new_window.show(self)

        if widget:
            name = widget.get_buffer().meta.name
            label = Tab_Label(name, widget)
            new_window.target.set_title("Editor: %s" % (name))
            new_window.target.tab_widget.set_tab_detachable(widget, True) 
            new_window.pages.append(widget)   
        elif files:
            for file in files:
                new_window.target.new_tab(file)
        else:
            buffer = self.new_buffer(None)
            view = self.new_view(buffer)
            self.pages.append(view)
        self.windows.add(new_window)
        return new_window.target        

    def add_page(self, window, text_buffer):
        """Adds a new view to the specfied window."""
        new_page = self.new_view(text_buffer)
        window_meta = self.find_window(target = window)
        window_meta.pages.append(new_page)
        new_page.show(window_meta)
        return new_page.target

    def remove_window(self, window):
        """Removes window objects with the specified target."""
        self.windows.remove(self.find_window(target = window))

    def new_buffer(self, path, version=None, is_unix_file=False, command=None):
        """Adds a new source buffer if not aleady open."""
        new_buffer = None
        if path:
            for buffer in self.buffers:
                if buffer.path == path and buffer.version == version:
                    new_buffer = buffer
                    break
            if (not new_buffer) and path in self.buffers:
                new_buffer = self.buffers[path]
        if not new_buffer:
            new_buffer = self.Buffer(path)
        if path or command:
            new_buffer.load(version, is_unix_file, command)
        self.buffers.add(new_buffer)

        if not new_buffer.target:
            new_buffer.target = Source_Buffer(None)

        return new_buffer.target

    def remove_buffer(self, target):
        """Removes buffer objects with the specified target."""
        buffer = self.find_buffer(target = target)
        self.buffers.remove(buffer)

    def new_view(self, buffer):
        """Adds a view object and associates it with the specified buffer."""
        buffer_state = self.find_buffer(target = buffer)
        if buffer_state:
            new_view = None
            new_view = self.View()
            new_view.buffer = buffer_state
            new_view.buffer_id = buffer_state.id
            self.views.add(new_view)
        else:
            log.log.append(log.Event('Warning',
                                       "Failed to find buffer (%s) in context %s" % (str(buffer), self.name)))

        return new_view

    def remove_view(self, target):
        """Removes view objects with the specified target."""
        view = self.find_view(target = target)
        self.views.remove(view)

        if view:
            text_window = target.get_view()
            position = text_window.get_visible_rect()
            text_iter = text_window.get_iter_at_location(position.x, position.y)
            view.offset = str(text_iter.get_offset())
            view.pane_width = str(target.get_position())

    def remove_page(self, window, page):
        """Remove the specified page from the window."""
        page = self.find_view(target = page)
        window = self.find_window(target = window)
        if page and window:
            window.remove_page(page)

    def parse_node(self, node):
        """Load the data for this context from the supplied dom node."""
        for child in node.childNodes:
            if child.tagName == 'name':
                self.name = str(child.firstChild.nodeValue).capitalize()
            elif child.tagName == 'descriptipn':
                self.description = child.firstChild.nodeValue
            elif child.tagName == 'frozen':
                self.frozen = bool(child.firstChild.nodeValue)
            elif child.tagName == 'window':
                new_window = self.Window()
                new_window.parse(child, self)
                self.windows.add(new_window)
            elif child.tagName == 'buffer':
                new_buffer = self.Buffer()
                new_buffer.parse(child)
                self.buffers.add(new_buffer)
            elif child.tagName == 'view':
                new_view = self.View()
                new_view.parse(child)
                self.views.add(new_view)

        # Create a reference to the actual buffer. 
        for view in [v for v in self.views]:
            buffer = self.find_buffer(id = view.buffer_id)
            if buffer:
                view.buffer = buffer
            else:
                log.log.append(log.Event('Warning', "cannot find buffer %s in view %s" % (str(view.buffer_id), str(view.id))))
                self.views.remove(view)

        # Replace the view ids with the corresponding view
        for window in self.windows:
            pages = []
            for page in window.pages[:]:
                view = self.find_view(id = page)
                if view:
                    pages.append(view)
                else:
                    log.log.append(log.Event('Warning', "cannot find view %s in window %s" % (str(page), str(window.id))))
                    window.pages.remove(page)
            window.pages = pages

    def to_dom_node(self, document):
        "Returns a string containing the xml for this context."""
        parent_node = document.createElement('context')
        name_node = document.createElement('name')
        name_node.appendChild(document.createTextNode(self.name))
        parent_node.appendChild(name_node)

        if self.description:
            description_node = document.createElement('description')
            description_node.appendChild(document.createTextNode(str(self.description)))
            parent_node.appendChild(description_node)

        frozen_node = document.createElement('frozen')
        frozen_node.appendChild(document.createTextNode(str(self.frozen)))
        parent_node.appendChild(frozen_node)
        for window in self.windows:
            if window.target:
                parent_node.appendChild(window.to_dom_node(document))
        for buffer in self.buffers:
            parent_node.appendChild(buffer.to_dom_node(document))
        for view in self.views:
            parent_node.appendChild(view.to_dom_node(document))
        return parent_node

    def close(self):
        """Closes all buffers, views windows associated with this context."""
        save_contexts(context_file)
        quit = True
        for buffer in self.buffers:
            quit = quit and buffer.target.confirm_close()

def load_contexts(context_file):
    """Reads and parses an xml file containing context data."""
    context_file = os.path.expanduser(context_file)
    pattern = re.compile("\s*(<|>)\s*")
    if os.path.exists(context_file):
        file = open(context_file)
        xml_string = file.read()
        file.close()
        # Remove pretty printing
        xml_string = pattern.sub(r'\1', xml_string)
        document = xml.dom.minidom.parseString(xml_string)
        # Remove 'empty' text nodes        
        for node in document.documentElement.childNodes:
            if node.tagName == 'active':
                config.contexts = node.firstChild.nodeValue.split(' ')
            if node.tagName == 'context':
                context = Context()
                context.parse_node(node)
                contexts[context.name] = context
        
def save_contexts(context_file):
    """Update the contexts file based on the current settings."""
    context_file = os.path.expanduser(context_file)
    xml_document = xml.dom.minidom.getDOMImplementation().createDocument(None, None, None)

    parent_node = xml_document.createElement('contexts')
    #contexts['Working'] = contexts['null']
    #contexts['Working'].name = 'Working'
    #contexts['Working'].frozen = True
    active_contexts = [context.name for context in contexts.values() if context.is_active]
    if active_contexts:
        active_node = xml_document.createElement('active')
        active_node.appendChild(xml_document.createTextNode(' '.join(active_contexts)))
        parent_node.appendChild(active_node)
    
    for context in contexts:
        parent_node.appendChild(contexts[context].to_dom_node(xml_document))
    xml_document.appendChild(parent_node)

    if not os.path.exists(os.path.dirname(context_file)):
        os.makedirs(os.path.dirname(context_file))
    file = open(context_file, 'w')
    file.write(xml_document.toprettyxml())
    file.close()

if __name__ == "__main__":
    os.chdir(os.path.split(sys.argv[0])[0])
    parser = OptionParser()
    parser.add_option("-f", "--file", dest = "config_file",
                      help = "load configuration file", default = defaults['config_file'],
                      metavar = "FILE")
    parser.add_option("-s", "--session", dest = "session_file",
                      help = "Session file to load")
    parser.add_option("-c", "--context", dest = "contexts",
                      help = "Context to load")

    (options, args) = parser.parse_args()
    config = Configuration(options)

    manager = gtk.recent_manager_get_default()

    language_manager = gtksourceview2.language_manager_get_default()
    language_manager.globs = {}
    for id in language_manager.get_language_ids():
        for pattern in language_manager.get_language(id).get_globs():
            language_manager.globs[pattern] = id

    filename = None
    root_path = os.path.split(sys.argv[0])[0]
    if root_path == '.':
        root_path = os.getcwd()

    if config.context_file:
        load_contexts(config.context_file)
    contexts['null'] = Context('null')

    if options.contexts:
        config.settings['contexts'] = options.contexts.split(',')

    context_active = False
    for context in config.contexts:
        if context in contexts:
            contexts[context].activate(args)
            context_active = True

    if not context_active:
        context = Context('Working')
        context.activate(args)

    gtk.main()
    file_monitor.stop_all()
